Delay with millis: unexplicable behaviour

A program that compiles ok and runs well needs an additional (third) delay with millis.

There already are two delays with millis running flawless; adding a third seems impossible: the program just quits running, even the LCD message during setup is not being displayed.

The questionable lines:

A. in declarations:

unsigned long previousMillis3 = 0; // display timer 3
const int displaydelay = 500; // display update delay in milliseconds

B. ..the added (questionable) llines in loop(); currently commented out in the main code, lower:

  unsigned long currentMillis = millis();

 if (currentMillis - previousMillis3 >= displaydelay) 
{
// current code here

   previousMillis3 = currentMillis;
}

The program:

#include <Wire.h>
#include <LCD.h> // will force using NewLiquidCrystal library
#include <LiquidCrystal_I2C.h>
#include <Adafruit_INA219.h>
Adafruit_INA219 ina219; //declare an instance of INA219
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
// const int RELAIS = 10;
#define RELAIS 10 // PB2 set relais/LED output on pin 10 overtemp detection
#define BEEP 11 // PB3 set piezo beep output on pin 11
// int RELAIS = 10;
const int T1 = 70; // max normal operating temperature darlington & heatsink
const int T2 = 85; // overtemperature darlington or heatsink
const int T2a = T2 - 2;  // hysteresis set variable overtemperature at -2C
int T2b = T2; // hysteresis set variable overtemperature
const int T3 = 65; // kick-in temp for fan, fanspeed = 25%
const int T4 = 85; // temp for max fan speed
const int T5 = 5; // hysteresis value, used to substract from kick-in temperature
int fanValue; // output value to cooling fan
int T6; // intermediate temp value for fan speed usage
const int fanMin = 125; // minimum fan speed
const int fanMax = 255; // maximum fanspeed
const int fan = 9; // ventilator output pin
const int V1 = 2; // max voltage when short circuit occurs
const int C1 = 2; // min current when short circuit occurs
const int displaydelay = 500; // display update delay in millis
boolean OC; // overcurrent status
boolean warning; // warning status (temperature and/or current)
const int x = 8; // loop value: loop amount = x+2 because min and max are removed
const long interval1 = 500; // display time 1 in milliseconds
const long interval2 = 1000; // display time 2 in milliseconds
int long interval;
unsigned long previousMillis1 = 0; // display timer 1
unsigned long previousMillis2 = 0; // display timer 2
unsigned long previousMillis3 = 0; // display timer 3
boolean message1;
boolean message2;
unsigned long periods[] = {500, 1500};
unsigned int signalMax0 = 0; // max value from analog 0
unsigned long int signalMin0 = 150000; // min value from analog 0, temp1 Darlington
unsigned int signalMax1 = 0; // max value from analog 1
unsigned long int signalMin1 = 150000; // min value from analog 1, temp2 heatsink
unsigned int signalMax2 = 0; // max value from analog 2
unsigned long int signalMin2 = 450000; // min value from analog 2, output voltage

unsigned long now;
float cumulVoltage = 0;
float cumulCurrent = 0;
unsigned long int cumul = 0;

void setup()
{
  //initialise libraries and ancillaries
  Serial.begin(9600);
  delay(250);
  Serial.println(__FILE__);
  analogReference(EXTERNAL);  // externe referntiespanning: 5,000V
  ina219.begin();
  lcd.begin(16, 2);
  pinMode (RELAIS, OUTPUT);
  pinMode (BEEP, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(fan, OUTPUT); //set fan as ventilator output
  digitalWrite(RELAIS, LOW);
  digitalWrite(BEEP, LOW);
  // LCD Backlight ON
  lcd.backlight();
  lcd.setBacklight(HIGH);

  //initialise display on startup
  lcd.home (); // go home on LCD
  lcd.print("    Labo PSU");
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  lcd.setCursor (0, 1);
  lcd.print("    Erik /V9    ");
  digitalWrite(LED_BUILTIN, LOW);
  delay(100);
  for (int i = 0; i < 3; i++)
  {
    lcd.noBacklight();
    digitalWrite(LED_BUILTIN, HIGH);
    delay(100);
    lcd.backlight();
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
  }
}

void loop()
{

  //initialise variables and reset loop totals to 0
  unsigned long int temp1Total = 0; // temp1 darlington
  unsigned long int temp2Total = 0; // temp2 heatsink
  unsigned long int voltageTotal = 0;
  unsigned long int currentTotal = 0;
  unsigned long currentMillis = millis();
  float voltageDisplay;
  float currentDisplay;
  unsigned long int temp1Value; // temp1 darlington
  unsigned long int temp2Value; // temp2 heatsink
  unsigned int voltageValue; // spanning
  unsigned int currentValue; // stroom
  static char outvolt[7];
  static char outcurr[7];
  static char outtemp1[8];
  static char outtemp2[8];
  unsigned int signalMax0 = 0; // max value from analog 0
  unsigned long int signalMin0 = 150000; // min value from analog 0, temp1 Darlington
  unsigned int signalMax1 = 0; // max value from analog 1
  unsigned long int signalMin1 = 150000; // min value from analog 1, temp2 heatsink
  unsigned int signalMax2 = 0; // max value from analog 2
  unsigned long int signalMin2 = 450000; // min value from analog 2, output voltage
  int fanValue; // output value to cooling fan
  int T6; // intermediate temp value for fan speed usage

  //start loop x + 2 times
  // x + 2 measurements of each analog input and accumulation;
  // test conditions for emergency action where and when necessary
  for (int j1 = 0; j1 < (x + 2); j1++)
  {
    //read values from analog inputs and accumulate
    temp1Value = (analogRead(0) * 50000 / 1024); // darlington temp read m°C
    temp2Value = (analogRead(1) * 50000 / 1024); // heatsink temp read m°C
    // voltageValue = (analogRead(2) * 45000 / 1024); // mV
    // voltageValue = (analogRead(2) * 45000 / 1024); // mV
    // delayMicroseconds(1000);
    voltageValue = (analogRead(2) * 45000 / 1024); // mV
    currentValue = ina219.getCurrent_mA(); //mA
    if (temp1Value < signalMin0)signalMin0 = temp1Value;
    if (temp1Value > signalMax0)signalMax0 = temp1Value;
    if (temp2Value < signalMin1)signalMin1 = temp2Value;
    if (temp2Value > signalMax1)signalMax1 = temp2Value;
    if (voltageValue < signalMin2)signalMin2 = voltageValue;
    if (voltageValue > signalMax2)signalMax2 = voltageValue;
    temp1Total += temp1Value; // temp1 darlington accum m°C*10
    temp2Total += temp2Value; // temp2 cooling fins accum m°C*10
    voltageTotal += voltageValue; // spanning mV*10
    currentTotal += currentValue; // stroom mA

    //test conditions for overtemp and or overcurrent warnings
    if (voltageValue / 1000 < V1 && currentValue / 1000 > C1) // shortcircuit
    {
      OC = true;
      lcd.setCursor (0, 1);
      lcd.print("kortsluiting    ");
      digitalWrite(LED_BUILTIN, HIGH);
    }
    else                                 // no shortcircuit;
    {
      OC = false;
      if (warning == false)
      {
      }
    }
  }

  //remove minimum and maximum outliers
  temp1Total -= signalMin0; // darlington temp m°C
  temp1Total -= signalMax0;
  temp2Total -= signalMin1; // heatsink temp m°C
  temp2Total -= signalMax1;
  voltageTotal -= signalMin2; // output voltage mV
  voltageTotal -= signalMax2;

  //calculate average temperatures, voltage and current
  temp1Value = temp1Total / x / 100; // temp darlington °C
  temp2Value = temp2Total / x / 100; // temp heatsink °C
  voltageDisplay = voltageTotal / 1000.0 / x;
  currentDisplay = currentTotal / 1000.0 / (x + 2);

  cumulVoltage += voltageDisplay;
  cumulCurrent += currentDisplay;
  cumul = cumul + 1;

  //fan cooling ventilator control section, output on pin 9
  bool fanOn = false;
  if (temp1Value > T4) //then it's already at max speed...
  {
    T6 = 100;
  }
  if (temp1Value > T3) // min temp1Value for fan kick-in
  {
    fanOn = true;
    T6 = temp1Value;
  }
  else if (temp1Value < (T3 - T5)) // min temp1Value - hysteresis for fan kick-out
  {
    fanOn = false;
    analogWrite(fan, 0);
  }
  if (fanOn)
  { // calculate fanspeed if temp1Value > T3
    if (temp1Value > T3)
    {
      T6 = temp1Value;
      fanValue = map(T6, T3, T4, fanMin, fanMax);
      analogWrite(fan, fanValue);
    }
    else {
      analogWrite(fan, fanMin);    // fanspeed if T3 - T5 < temp1Value < T3
    }
  }

  //prepare values for display
//  if (currentMillis - previousMillis3 >= displaydelay)  // only update display every displaydelay delay to limit flicker
//  {
  dtostrf(voltageDisplay, 5, 2, outvolt); // display output voltage: 5 digits where 2 after comma
  dtostrf(currentDisplay, 5, 2, outcurr); //add 2 to x because no removal minimum and maximum values from current
  dtostrf(temp1Value, 3, 0, outtemp1);    //display darlington temp
  dtostrf(temp2Value, 3, 0, outtemp2);    //display heatsink temp
  lcd.home ();
  lcd.print("U=");
  lcd.print(outvolt);
  lcd.print("V");
  lcd.print(" T1=");
  lcd.print(outtemp2); // heatsink temp on line 1
  lcd.print("C");
  lcd.setCursor (0, 1); // go to start of 2nd line
  if (warning == false && OC == false)
  {
    digitalWrite(LED_BUILTIN, LOW);
    lcd.print("I=");
    lcd.print(outcurr);
    lcd.print("A");
    lcd.print(" T2=");
    lcd.print(outtemp1);
    lcd.print("C");
  }
//    previousMillis3 = currentMillis;
//  }


  //test for overtemp conditions
  if ((temp1Value >= T1 && temp1Value < T2b && OC == false) || (temp2Value >= T1 && temp2Value < T2b && OC == false))  // warning high temp darlington or heatsink
  {
    T2b = T2; // hysteresis set back to value 0C = T2
    warning = true;
    // lcd.setCursor (0, 1); // go to start of 2nd line

    if ((currentMillis - previousMillis1) >= interval)
    {
      previousMillis1 = currentMillis;
      if (message1 == true)
      {
        message1 = false;
        interval = interval1;
        lcd.print("HIGH TEMP WARN!!");
        digitalWrite(LED_BUILTIN, HIGH);
      }
      else
      {
        message1 = true;
        digitalWrite(LED_BUILTIN, LOW);
        interval = interval2;
        lcd.print("I=");
        lcd.print(outcurr);
        lcd.print("A");
        lcd.print(" T2=");
        lcd.print(outtemp1);
        lcd.print("C");
      }
    }
  }

  if ((temp1Value >= T2b && OC == false) || (temp2Value >= T2b && OC == false))  // warning overtemp darlington or heatsink
  {
    T2b = T2a; // hysteresis st -2C
    // lcd.setCursor (0, 1); // go to start of 2nd line
    warning = true;
    digitalWrite(RELAIS, HIGH);         // overtemperature, relay opens

    if ((currentMillis - previousMillis2) >= interval)
    {
      previousMillis2 = currentMillis;

      if (message2 == true)
      {
        message2 = false;
        interval = interval1;
        lcd.print("OVERTEMP WARNING");
        digitalWrite(LED_BUILTIN, HIGH);
        digitalWrite(RELAIS, HIGH);         // overtemperature, relay opens
        digitalWrite(BEEP, HIGH);           // overtemperature, beep sounds
        tone(BEEP, 4000);
      }
      else
      {
        message2 = true;
        interval = interval2;
        digitalWrite(LED_BUILTIN, LOW);
        digitalWrite(BEEP, LOW);
        noTone(BEEP);
        lcd.print("I=");
        lcd.print(outcurr);
        lcd.print("A");
        lcd.print(" T2=");
        lcd.print(outtemp1);
        lcd.print("C");
      }
    }
  }

  if (temp1Value < T1 && temp2Value < T1 && OC == false)   // temperatures back to normal, protection relay and beep reset
  {
    lcd.setCursor (0, 1);
    digitalWrite(RELAIS, LOW);
    digitalWrite(LED_BUILTIN, LOW);
    warning = false;
  }
}

Which Arduino are you using?

@jremington You are spot on!! I used an ATmega168P, and global variables use 77% of available memory, I just discovered.
When running on a ATmega328 no problem.

I have a stack of 168P's I would like to use up, so please tell me how to reduce the global variables, if possible, in this program?

What does this mean?

What version of

are you using? The ones I know of only have I2C constructors. (There are many versions using the same name with varying implementations.) Maybe finding one with less overhead would help.

I use malpartitda's LCD library and that one line makes sure that compilation is ok.

Judicious use of the F-macro would help. If your LCD library has support.

All the Constant int's that are 255 and lower can be made into bytes.

I'll add that making variables local in the loop() will not help. They are still always there using up space but the IDE doesn't tell you about it.

If it doesn't, it means it doesn't inherit from Print. In that case pick a different one.

Bill Perry's hd44780 library is available through the library manager and has a lighter footprint. It is the preferred option for the i2c lcd displays. It is plug and play and will detect the i2c address and the pin configuration of the 44780 chip. There is even a diagnostic sketch if things are not working correctly.

Thank you @cattledog , @gfvalvo , @Hutkikz , @Coding_Badly , @oldcurmudgeon , @jremington for your kind assistance.

All your comments will be implemented.

Someone had said (but that comment seems to have disappeared in this topic) to use arrays.
What variables would be recommended for use in an array here?

Since you are using ATmega processors, I'm wondering how lines like the following ever worked.

temp1Value = (analogRead(0) * 50000 / 1024); // darlington temp read m°C

The default for signed integer arithmetic with AVR processors is 16 bits (with a maximum positive value of 32767), so the multiplication by 50000 on the right hand side overflows.

To make sure that it doesn't, force the calculation to be performed as unsigned long, as follows:

temp1Value = (analogRead(0) * 50000UL / 1024); // darlington temp read m°C

I suspect the compiler treats the 50000 as 50000L. C is quirky that way.

Which is obviously the far better choice.

Not in my experience. Indeed the Arduino reference on Integer Constants is explicit about the formatters.

https://www.arduino.cc/reference/tr/language/variables/constants/integerconstants/

Notes and Warnings

U & L formatters:

By default, an integer constant is treated as an int with the attendant limitations in values. To specify an integer constant with another data type, follow it with:

  • a 'u' or 'U' to force the constant into an unsigned data format. Example: 33u
  • a 'l' or 'L' to force the constant into a long data format. Example: 100000L
  • a 'ul' or 'UL' to force the constant into an unsigned long constant. Example: 32767ul

I assume the folks who wrote this do a reasonable job of following the C++ standard...

The type of the integer literal is the first type in which the value can fit...

50000 has no suffix and does not fit in an int. The avr-gcc compiler will choose long int. Looks like Microsoft's C++ documentation and Google's AI agree.

(I really should have written "C++" instead of "C" in that post.)

In which case, you need a better compiler. :wink:

Regardless, including the suffix is the right choice.

The arduino doc is not a great reference for many things. They take shortcuts and simplify the reality.

In C++, integer literals are assigned a type based on their value and any suffix they may have.

it's true that by default, an integer literal is considered of type int unless there is a suffix to modify that.

But If the value is too large to fit in an int, the compiler attempts to fit it into a long int, and if necessary, into a long long int. If the value is still too large for long long int, then you get a compilation error.

To explicitly specify the type, suffixes can be used: U or u for unsigned, L or l for long, and LL or ll for long long.

on a platform where int are represented using 2 bytes

32767                   // int (fits)  
-32768                  // int (fits)  
32768                   // long (since it exceeds 2-byte int)  
65535                   // long 
-9223372036854775808    // long long (since it exceeds 4-byte long)  
9223372036854775807     // long long (since it exceeds 4-byte long)  

where the proverbial sh*t hits the fan is when you do operations

unsigned long oneMinute =  60 * 1000; // 60 seconds in milliseconds

Because here 60 and 1000 do fit in an int, and so the multiplication is conducted as a 2 byte int operation and will overflow / rollover and that's the result which will be assigned (byte for byte) into the correct expected type unsigned long (so wrong value).

That's where you need to be careful and force the operation to be conducted with unsigned long by having at least one of the operand as an unsigned long (then the other one gets promoted to unsigned long)

unsigned long oneMinute =  60 * 1000ul; // 60 seconds in milliseconds

Sorry for repeating what @Coding_Badly said - I swear his answer was not visible when I typed that in... weird.

Do you need separate string variables for each of these? Could you not have one string and re-use it?

Not sure why you made these static. They don't appear to need to be static.

Not in this case, I agree.

But in a few edge-cases it might. For example if setup() needed to use a large amount of variables while it was running, and then loop() also needed a large amount of variables. setup() could have more variables because loop()'s variables won't have been created at that time. Later, loop() could also have lots of variables because those variables created in setup() have been destroyed by then.

But once loop() begins running, it's variables are there to stay. If loop() calls some other function, loop()'s variables don't disappear. loop()'s variables get destroyed when loop() ends, but then loop() immediately gets run again and re-creates the variables, so in effect they are there full time.

Yes, that's why sometimes it's good to make them static so that the compiler reports the used memory. Can help troubleshoot if you are low on RAM.

@brice3010 what you may be able to do, to save memory, is to make all those variables even more local.

Some variables may only be needed in some parts of the code. If you make them local to that part of the code, the memory the variables consume can be released as soon as that part of the code is done. That frees up the memory for use by subsequent parts of the code.

You could do that by splitting up the code from loop() into multiple functions. Then have loop() call those functions. Variables declared inside those functions will get destroyed when the function ends, freeing up some memory for the next function and so on.

But take care when using static because static variables get allocated memory permanently. Use them only when you know you really need to.