Program hangs/slow response after a while

Hi all, hope everyone's having a nice week so far !
So i just created my account so might have limited input capabilities so please bear with me if it's the case!
Long story short is that i have created an espresso machine digital thermostat kinda, with some additional logic to it and i stumbled into a problem, after a while into it's operation the whole program either hangs or becomes slow, i thought it must be a low memory issue and after some research i did add the memory monitoring function and writing it's value to the display and it doesn't seem to be a memory issue as it shows plenty of available bytes.

My setup is the following:

  1. Arduino nano
  2. Nextion 2.4" basic lcd
  3. Max6675 thermocouple

It seems there's loads of memory available and not sure why it just comes to a crawl after a while of operation, maybe there's something i'm completely missing or the code is plain crap please guide me if it's the case, i have debugged a lot and can't seem to find the culprit of the issue.

Arudino code

Edited:

  1. Excluded the useless 777777 checks since the data types used aren't long enough
  2. Changed the subforum from programming questions to Displays as it might be a Nextion library issue.
// #include <RBDdimmer.h>
#include <EEPROM.h>
#include <trigger.h>
#include <EasyNextionLibrary.h>
#include <max6675.h>


// Define our pins
#define thermoDO 4
#define thermoCS 5
#define thermoCLK 6
//#define vibrPin 7 // PD7
#define relayPin 8  // PB0

//RAM debug
extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;


//Init the thermocouple with the appropriate pins defined above with the prefix "thermo"
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);
EasyNex myNex(Serial);



// Global var  - just defined globally to avoid reading the temp sensor in every function i'm using it for
float currentTempReadValue = 0.00;
byte setPoint = 0;
byte offsetTemp = 0;
uint16_t brewTimeDelayTemp = 0;
byte brewTimeDelayDivider = 0;
char waterTempPrint[6];
unsigned long timer = 0;

// EEPROM  stuff - alo global cause used in multiple functions
int EEP_ADDR1 = 1, EEP_ADDR2 = 20, EEP_ADDR3 = 40, EEP_ADDR4 = 60;


void setup() {
  myNex.begin(9600);
  //Registering a higher baud rate for quicker serial communication
  Serial.print(F("baud=115200"));
  Serial.write(0xff);
  Serial.write(0xff);
  Serial.write(0xff);
  Serial.end();
  Serial.begin(115200);

  // relay port init and set initial operating mode
  // pinMode(vibrPin, INPUT);
  pinMode(relayPin, OUTPUT);

  // Chip side  HIGH/LOW  specification
  // PORTB |= _BV(PB0);
  //  PORTB &= ~_BV(PB0); slow
  // PORTB |= _BV(PB0); //relayPin HIGH
  PORTB &= ~_BV(PB0);  // relayPin LOW

  // Will wait hereuntil full serial is established, this is done so the LCD fully initializes before passing the EEPROM values
  delay(500);
  // Applying the EEPROM saved values
  uint8_t tmp1 = 0;
  uint8_t tmp2 = 0;
  uint16_t tmp3 = 0;
  uint8_t tmp4 = 0;

  // Making sure we're getting the right values and sending them to the display
  tmp1 = readIntFromEEPROM(EEP_ADDR1);
  if ( !(tmp1<0)  && tmp1 != NAN) {  // 777777 is the return value if the incoming value is malformed
    myNex.writeNum("page1.n0.val", tmp1);
  }
  tmp2 = readIntFromEEPROM(EEP_ADDR2);
  if ( !(tmp2<0)  && tmp2 != NAN) {  // 777777 is the return value if the incoming value is malformed
    myNex.writeNum("page1.n1.val", tmp2);
  }
  tmp3 = readIntFromEEPROM(EEP_ADDR3);
  if ( !(tmp3<0)  && tmp3 != NAN) {  // 777777 is the return value if the incoming value is malformed
    myNex.writeNum("page2.n0.val", tmp3);
  }
  tmp4 = readIntFromEEPROM(EEP_ADDR4);
  if ( !(tmp4<0)  && tmp4 != NAN) {  // 777777 is the return value if the incoming value is malformed
    myNex.writeNum("page2.n1.val", tmp4);
  }
  myNex.writeNum("page0.n1.val", getFreeSram());
}


//Main loop where all the bellow logic is continuously run
void loop() {
  myNex.NextionListen();
  // Reading the temperature every 250ms between the loops
  if (millis() >= timer + 250UL) {
    timer += 250UL;
    currentTempReadValue = thermocouple.readCelsius();
    if (currentTempReadValue == NAN || currentTempReadValue < 0) currentTempReadValue = thermocouple.readCelsius();  // Making sure we're getting a desired value
  }
  doCoffee();
  myNex.writeNum("page0.n1.val", getFreeSram());
}


//  ALL used functions declared bellow
// The *precise* temp control logic
void doCoffee() {
  // Making sure the serial communication finishes sending all the values
  setPoint = myNex.readNumber("page1.n0.val");  // reading the setPoint value from the lcd
  if (!(setPoint<0) && setPoint != NAN && setPoint > 85) {
    delay(0);
  } else {
    setPoint = myNex.readNumber("page1.n0.val");
  }

  offsetTemp = myNex.readNumber("page1.n1.val");  // reading the offset value from the lcd
  if ( !(offsetTemp<0) && offsetTemp != NAN ) {
    delay(0);
  } else {
    offsetTemp = myNex.readNumber("page1.n1.val");
  }

  brewTimeDelayTemp = myNex.readNumber("page2.n0.val");  // reading the brew time delay used to apply heating in waves
  if ( !(brewTimeDelayTemp<0) && brewTimeDelayTemp != NAN ) {
    delay(0);
  } else {
    brewTimeDelayTemp = myNex.readNumber("page2.n0.val");
  }

  brewTimeDelayDivider = myNex.readNumber("page2.n1.val");  // reading the delay divider
  if ( !(brewTimeDelayDivider<0) && brewTimeDelayDivider != NAN ) {
    delay(0);
  } else {
    brewTimeDelayDivider = myNex.readNumber("page2.n1.val");
  }
  // Getting the boiler heating power range
  uint16_t powerOutput = map(currentTempReadValue, setPoint - 10, setPoint, brewTimeDelayTemp, brewTimeDelayTemp / brewTimeDelayDivider);
  if (powerOutput > brewTimeDelayTemp) {
    powerOutput = brewTimeDelayTemp;
  } else if (powerOutput < brewTimeDelayTemp / brewTimeDelayDivider) {
    powerOutput = brewTimeDelayTemp / brewTimeDelayDivider;
  }

  // Applying the powerOutput variable as part of the relay switching logic
  if (currentTempReadValue < (float)setPoint - 10.00 && !(currentTempReadValue < 0.00) && currentTempReadValue != NAN) {
    PORTB |= _BV(PB0);  // relayPIN -> HIGH
  } else if (currentTempReadValue >= (float)setPoint - 10.00 && currentTempReadValue < (float)setPoint - 3.00) {
    PORTB |= _BV(PB0);   // relayPIN -> HIGH
    delay(powerOutput);  // delaying the relayPin state for <powerOutput> ammount of time
    PORTB &= ~_BV(PB0);  // relayPIN -> LOW
  } else if (currentTempReadValue >= (float)setPoint - 3.00 && currentTempReadValue <= float(setPoint - 1.00)) {
    PORTB |= _BV(PB0);   // relayPIN -> HIGH
    delay(powerOutput);  // delaying the relayPin state for <powerOutput> ammount of time
    PORTB &= ~_BV(PB0);  // relayPIN -> LOW
    delay(powerOutput);  // delaying the relayPin state for <powerOutput> ammount of time
  } else if (currentTempReadValue >= (float)setPoint - 1.00 && currentTempReadValue < (float)setPoint - 0.50) {
    PORTB |= _BV(PB0);   // relayPIN -> HIGH
    delay(powerOutput);  // delaying the relayPin state for <powerOutput> ammount of time
    PORTB &= ~_BV(PB0);  // relayPIN -> LOW
    delay(powerOutput);  // delaying the relayPin state for <powerOutput> ammount of time
  } else {
    PORTB &= ~_BV(PB0);  // relayPIN -> LOW
  }

  // Updating the LCD
  if (currentTempReadValue < 115.00) {
    if (powerOutput > brewTimeDelayTemp) {
      powerOutput = brewTimeDelayTemp;
    } else if (powerOutput < brewTimeDelayTemp / brewTimeDelayDivider) {
      powerOutput = brewTimeDelayTemp / brewTimeDelayDivider;
    } else {
      myNex.writeNum("page0.n0.val", powerOutput);
    }
    myNex.writeNum("page0.n0.val", powerOutput);
  } else if (currentTempReadValue > 115.00 && !(currentTempReadValue < 0) && currentTempReadValue != NAN) {
    static bool blink = true;
    static unsigned long timer_s1 = 0, timer_s2 = 0;
    unsigned long currentMillis = millis();
    if (blink == true) {
      if (currentMillis >= timer_s1 + 1000UL) {
        timer_s1 += 1000UL;
        blink = false;
      }
      myNex.writeStr("vis t1,1");
      myNex.writeNum("page0.n0.val", NAN);
      dtostrf(currentTempReadValue - (float)offsetTemp, 6, 2, waterTempPrint);
      myNex.writeStr("page0.t0.txt", waterTempPrint);
    } else {
      myNex.writeStr("vis t1,0");
      myNex.writeNum("page0.n0.val", NAN);
      dtostrf(currentTempReadValue - (float)offsetTemp, 6, 2, waterTempPrint);
      myNex.writeStr("page0.t0.txt", waterTempPrint);
      if (currentMillis >= timer_s2 + 1000UL) blink = true;
    }
  } else {
    myNex.writeNum("page0.n0.val", NAN);
  }
  dtostrf(currentTempReadValue - (float)offsetTemp, 6, 2, waterTempPrint);
  myNex.writeStr("page0.t0.txt", waterTempPrint);  // Printing the current water temp values to the display
}

// EEPROM WRITE
void writeIntIntoEEPROM(int address, int number) {
  EEPROM.write(address + 1, number & 0xFF);
  EEPROM.write(address, number >> 8);
}
//EEPROM READ
int readIntFromEEPROM(int address) {
  return (EEPROM.read(address) << 8) + EEPROM.read(address + 1);
}

// Save the desired temp values to EEPROM
void trigger1() {
  int savedBoilerTemp = myNex.readNumber("page1.n0.val");
  int savedOffsetTemp = myNex.readNumber("page1.n1.val");
  int savedBrewTimeDelay = myNex.readNumber("page2.n0.val");
  int savedbrewTimeDivider = myNex.readNumber("page2.n1.val");

  if (EEP_ADDR1 >= 19) EEP_ADDR1 = 1;
  if (EEP_ADDR2 >= 39) EEP_ADDR1 = 20;
  if (EEP_ADDR3 >= 59) EEP_ADDR1 = 40;
  if (EEP_ADDR4 >= 79) EEP_ADDR1 = 60;

  // Reading our values from the LCD and checking whether we got proper serial communication
  if (savedBoilerTemp != 777777 && savedBoilerTemp > 0) {  // 777777 is the return value if the incoming value is malformed
    writeIntIntoEEPROM(EEP_ADDR1, savedBoilerTemp);
    myNex.writeStr("popupMSG.t0.txt", "SUCCESS!");
    myNex.writeStr("page popupMSG");
    delay(50);  //giving it a bit of time to finish the serial comm
  }
  if (savedOffsetTemp != 777777 && savedOffsetTemp > 0) {  // 777777 is the return value if the incoming value is malformed
    writeIntIntoEEPROM(EEP_ADDR2, savedOffsetTemp);
    myNex.writeStr("popupMSG.t0.txt", "SUCCESS!");
    myNex.writeStr("page popupMSG");
    delay(50);  //giving it a bit of time to finish the serial comm
  }
  if (savedBrewTimeDelay != 777777 && savedBrewTimeDelay > 0) {  // 777777 is the return value if the incoming value is malformed
    writeIntIntoEEPROM(EEP_ADDR3, savedBrewTimeDelay);
    myNex.writeStr("popupMSG.t0.txt", "SUCCESS!");
    myNex.writeStr("page popupMSG");
    delay(50);  //giving it a bit of time to finish the serial comm
  }
  if (savedbrewTimeDivider != 777777 && savedbrewTimeDivider > 0) {  // 777777 is the return value if the incoming value is malformed
    writeIntIntoEEPROM(EEP_ADDR4, savedbrewTimeDivider);
    myNex.writeStr("popupMSG.t0.txt", "SUCCESS!");
    myNex.writeStr("page popupMSG");
    delay(50);  //giving it a bit of time to finish the serial comm
  }
}

// Vibrtion sensor
// bool vibrSense() {
//   bool vibration_detect = false;
//   unsigned long vibration = pulseIn(vibrPin, HIGH);
//   if (vibration > 2000) vibration_detect = true;
//   return vibration_detect;
// }

uint16_t getFreeSram() {
  uint8_t newVariable;
  // heap is empty, use bss as start memory address
  if ((uint16_t)__brkval == 0)
    return (((uint16_t)&newVariable) - ((uint16_t)&__bss_end));
  // use heap end as the start of the memory address
  else
    return (((uint16_t)&newVariable) - ((uint16_t)__brkval));
}

Sadly can't attach the nextion display .hmi file as i'm new here.

P.S. I did find the post with the tutorial from @PerryBebbington on how to use the nextion displays with arduino i was just already too deep into using the easyNextionLibrary by @Seithan and i wasn't thinking it's going to be such an issue ( assuming this is the issue ).

This won’t affect your performance today, but may show some problems with long run times…
Check out the examples to avoid millis() overflow issues.

delay(0) isn’t going to help much…

With EEPROM usage, look into EEPROM.put and .get to simplify your code… also it is a good idea to check the EEPROM library us ‘ready’ before writing, or it nay be lost.

1 Like

Thanks for your reply, will definitely have a look into the millis() buff overflow issue though since i'm not keeping the machine on for days i guess it's fairly safe to say i'm not experiencing this issue, at most maybe it operates for 2 hours in the morning.

As for delay(0) i just put it there to have something i know it's useless lol.

Will definitely look into changing the EEPROM stuff i really was mostly focusing on the program behaviour and the weird lockup.

All the != 777777 statements are just useless, because they will always be true.

There is no uint8_t or uint16_t that could ever get that value.

1 Like

Shit you're right this has slipped my eyes, originally they were int values but as i knew i'm dealing with small numbers i changed to a smaller type and totally overlooked the conditional statements.

It doesn't help with the hanging behaviour but does leave the code in a cleaner state so i appreciate any input you guys give here !
Also it's a strange behaviour where it works all good for a while then becomes really non responsive, the currentTempReadValue doesn't really seem to update or if it does it's really slow, thinking it might be a thermocouple issue and not code related at this point as i don't think i'm doing much at this point to have the system overloaded.

I don't know the Nextion library you are using,
maybe the receiving side is not a solid buffer based solution,
but some less deterministic String concatenation.

I see that the library takes a blocking approach, which I personally dislike.

My tutorial on How to write Timers and Delays in Arduino covers this.

I don't know the Nextion library you are using,
maybe the receiving side is not a solid buffer based solution,
but some less deterministic String concatenation.

I see that the library takes a blocking approach, which I personally dislike.

Thanks, i think you're right, i'll try to post the same question on the nextion subforum see what i get there.

Errr...
2 problems with that: posting the same question twice is against the forum rules because it means that you have people wasting their time answering in one place while someone else is answering somewhere else, and, there is no Nextion sub forum.

I don't know what is wrong with your code because I don't know that library, sorry.

I didn't post twice though, i edited the original post and changed the subforum from "Programming Questions" to "Displays", at least i hope this didn't create a second post but moved the original, sorry if it's not the case.

It's in the display section so I guess you did it right, thank you.

1 Like

Hello @vsparxx
I have see your code and I have some suggestions to add to the above recommendations from the other members.

#include <trigger.h>

You do not need to write that.

myNex.begin(9600);  //Registering a higher baud rate for quicker serial communication
  Serial.print(F("baud=115200"));
  Serial.write(0xff);
  Serial.write(0xff);
  Serial.write(0xff);
  Serial.end();
  Serial.begin(115200);

You only need myNex.begin(9600);
9600 is a good speed for transferring data for most projects. If you want to change to 115200, then from Nextion, you will need to write at the first page, to the Preinitialization Event: baud=115200 and change myNex.begin(115200);
That way, each time Nextion starts up, it will go to 115200 baud speed and it will not depend on Arduino's commands.
Also write at the first page, to the Preinitialization Event bkcmd=0

if (millis() >= timer + 250UL) {
    timer += 250UL;
    currentTempReadValue = thermocouple.readCelsius();
    if (currentTempReadValue == NAN || currentTempReadValue < 0) currentTempReadValue = thermocouple.readCelsius();  // Making sure we're getting a desired value
  }

For using millis() as a timeout, it is better to follow this way:

if ((millis() - timer) > 250UL){
// Do whatever you want
timer = millis();
}

Mind that at: unsigned long timer = 0;, when declaring the variable.
It is better like this: unsigned long timer = millis();

Also you can read the tutorial of @drmpf
5) From the library's documentation:

NOTE : As these commands are using the Serial port to read and write, it is more preferred not to run them in the loop() without delay(); or some other method of not running them with the frequency of the loop and use them only when it is needed. Using them in a loop, a delay in the loop can be noticed, especially when reading from the Serial. A Serial buffer overflow can also be caused.

In your code, you run the function doCoffee straight in the loop. As I can see, in this function, you run a lot of read and write commands through serial.
Also, you do not have to read the setpoints that you have set on screen every time that the loop() runs. It is more efficient to store the setpoints on local variables in your Arduino code and update them only when they are changed. For example, with a trigger() from a button, let's say save button.

void trigger5{
  setPoint = myNex.readNumber("page1.n0.val");  // reading the setPoint value from the lcd
  offsetTemp = myNex.readNumber("page1.n1.val");  // reading the offset value from the lcd
}

Inside the function doCoffee(), you have the updating of the page. You do not have to update the page that fast. I usually update the page every 500ms for fast changing values and for a project like yours, you can go to 1000ms and above.

But the most critical is that you have at least 3 pages and you are not using some method to check in which page you are on and send only the values that have to update. I can see this is why you are using global variables on Nextion

For this you can see the library's example from my site
https://seithan.com/Easy-Nextion-Library/Examples/#ChangePagesAndSendFloatValues

or my GitHub:
https://github.com/Seithan/EasyNextionLibrary/tree/master/examples/ChangePagesAndSendFloatValues

If you follow all the suggestions in this post by everyone, we all will soon be enjoying homemade coffee. Or better, Arduino made coffee :slight_smile:

1 Like

Thanks @Seithan for replying and your awesome insight, i was thinking it might be the case that i'm reading quite a lot through serial all the time :slight_smile: , the thinking behind it was that i can make temperature or other changes at any time and they will be picked up straight away without triggering any buttons, i usually only use the save button if i like the settings and i want to keep them.

Will try to get everything in line with what you wrote above and will update with hopefully a non locking setup and awesome cofee :smiley:

@Seithan - just wanted to thank you for your input it have helped immensely and now everything works smoothly!