Delay with millis: unexplicable behaviour

You could make a little function for printing floats to the LCD:

void printToLCD(float value, byte width, byte dp) {
  char out[10];
  dtostrf(value, width, dp, out);
  lcd.print(out);
}

then use it like this:

  lcd.home ();
  lcd.print("U=");
  printToLCD(voltageDisplay, 5, 2);
  lcd.print("V");
  lcd.print(" T1=");
  printToLCD(temp2Value, 3, 0); // heatsink temp on line 1
  lcd.print("C")

@PaulRB indeed, I wrote this program initially 9 years ago when I just started with C++. How should these four lines be written as one string to be reused? I guess I will dive into that program and rewrite plenty of lines.

@J-M-L @Coding_Badly @cattledog @PaulRB I very much enjoy your comments and learn a lot from them!

Maybe like post #21. Then you don't need those 4 strings. The string inside printToLCD() gets used each time and the memory it consumes is quickly released when the function ends.

Thanks @PaulRB I will get into rewriting this program with all these usefull comments here in mind.
I enjoy cramming code into my stack of 168P's, good use for old controllers.

I just realised that quite a few variables are declared twice, like this one:

unsigned long int signalMin0 = 150000; // min value from analog 0, temp1 Darlington

This is allowed because one is a global variable and the other is local to loop().

In theory, this could mean each repeated variable is taking twice as much memory. One of them, probably the one local to loop() is getting used. The other is not getting used.

In practice, the C compiler is probably detecting that the global variables are not getting used at all, and is ignoring them, and not allocating any memory to them.

But better to be safe than sorry and remove those duplications.

@PaulRB
But temp2Value is a unsigned long integer, so that should be a different function?

Like:

```
void printToLCD2(unsigned long int value, byte width, byte dp) {
  char out[10];
  dtostrf(value, width, dp, out);
  lcd.print(out);
}

..and..

lcd.home ();
  lcd.print("U=");
  printToLCD(voltageDisplay, 5, 2);
  lcd.print("V");
  lcd.print(" T1=");
  printToLCD2(temp2Value, 3, 0); // heatsink temp on line 1
  lcd.print("C")

Correct?

@PaulRB

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.

Would it make a noticeable difference in memory usage when calling a function for the return of all four variables (voltage, current, temperature1, temperature2) in one call versus having four different functions for each of the four variables?

Possibly, yes. A single function is going to need variables for the calculation of all 4 values. 4 separate functions will need only the variables for calculating of a single value. So the latter will need less memory than the former, and when called in sequence, the same memory will probably get re-used.

But this might only save a handful of bytes.

Your 168Ps may have only 1K bytes of memory, but you still don't know exactly what is using it all up. For example is it the LCD library? Would other similar libraries use significantly less memory?

When you have investigated that and learned what savings you could make, then you will know better whether you actually need to scrimp every byte by clever restructuring of your code.

What have you tried so far? Have you tried the F() macro suggestion, for example?

Oh, sorry, I thought they were all floats.

But I think my suggested code would still work because the long would get implicitly converted to a float.

Your suggestion would also work. In fact, you could probably give both functions exactly the same name, because their "signatures" are different. The signature of a function is the number and types of it's parameters. In C++, functions can have the same name provided their signatures are different.

void printToLCD2(unsigned long int value, byte width, byte dp) {
  char out[10];
  dtostrf(value, width, dp, out);
  lcd.print(out);
}

In your second function, dtostrf() is expecting a float as it's 1st parameter, and because it is given an unsigned long, an implicit conversion to float will happen. So actually there's no point having this second function. You might as well give an unsigned long to printToLCD() because the implicit conversion is going to happen either way.

Hi @PaulRB ...no the F() macro will be tested in the course of the day, I will post here.

I installed Optiboot for 168P and now have 15872bytes instead of 14336 bytes. But that does not help with RAM, I realise.
I made some changes in the program structure with the two delay-with-millis loops used for temperature control and the other for voltage, current measurements and LCD output.

Your suggestion of using a function for LCD calling reduced the RAM size quite a bit.

More to follow.

The use of the F-macro on all lcd.print statements reduced memory usage from 73% to 64%

for single characters like

        lcd.print("C");

better use

        lcd.print('C');

that will save one byte.

Then use lcd.write('C'); that will save one function call :slight_smile: (as print calls write)

Just to be noted that using "C" would get the cString in flash memory on « modern » MCUs (or use F() on older ones) and a const char * pointer would be passed to the print function so 2 bytes in flash (the letter and trailing null) and 2 or 4 bytes for the pointer in RAM (on the stack when calling print) versus 1 byte on the stack when calling with 'C' - so you save more than 1 byte depending on the architecture.

I tried

```
lcd.print(F('C'));
```

But compiler error.
Will the reduction in RAM by the use of the F-macro not be negated by the use of ' instead of " and without the F-macro?

the F() macro is only for c-strings (text within double quotes) - not just one char (single quote)

as explained above if you do

lcd.print('C');

then one byte is pushed on the stack by the compiler (so that byte is also in the binary code so you have one byte of flash memory there too).

if you do

lcd.print(F("C"));

then the compiler stores 2 bytes in flash for the c-string (the 'C' character and a trailing null character to denote the end of the string) and then will push on the stack (for the function call) the address in flash where the string is stored. This address is 2 or 4 bytes depending on the platform. Those 2 or 4 bytes are also in the binary, so eat up flash memory too.

➜ long story short, for one char you are better off with

lcd.write('C'); or lcd.print('C');

Strange, when changing one line from

lcd.print(F("C"));

to

lcd.print('C');

the used program memory nor used RAM changes after compile?

Program mem: 13494 bytes
Flash: 698 bytes

#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
#define fan 9     // ventilator output pin
unsigned long now1;
unsigned long now2;
unsigned long interval;
// unsigned long periods[] = {500, 1500};
const int interval1 = 500;  // delay time 1 in milliseconds, high temp alarm
const int interval2 = 750; // delay time 2 in milliseconds, overtemp alarm
const int interval3 = 1000; // delay time 3 in milliseconds, LCD refresh
uint8_t T1 = 70; // max normal operating temperature darlington & heatsink
const byte T1a = T1 - 2;  // hysteresis set variable high temperature low end at -2C
const byte T1b = T1; // hysteresis reset
const byte T2 = 85; // overtemperature darlington or heatsink
const byte T3 = 65; // kick-in temp for fan, fanspeed = 25%
const byte T4 = 85; // temp for max fan speed
const byte 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 byte fanMin = 125; // minimum fan speed
const byte fanMax = 254; // maximum fanspeed
const byte v1 = 2; // max voltage when short circuit occurs
const byte C1 = 2; // min current when short circuit occurs
const int displaydelay = 500; // display update delay in millis
boolean OC = false; // overcurrent status
boolean warning; // warning status (temperature and/or current)
const byte x = 8; // loop value: loop amount = x+2 because min and max are removed
boolean message1;
boolean message2;
// unsigned long now;
float voltageDisplay;
float currentDisplay;
unsigned long temp1Value; // temp1 darlington cumulated
unsigned long temp2Value; // temp2 heatsink cumulated
unsigned long voltageValue; // spanning cumulated
unsigned long currentValue; // stroom cumulated
unsigned long cumulVoltage;
unsigned long cumulCurrent;
byte cumul;

void setup()
{
  //initialise libraries and ancillaries
  Serial.begin(9600);
  delay(100);
  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(F("    Labo PSU"));
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  lcd.setCursor (0, 1);
  lcd.print(F("    Erik /V10a    "));
  digitalWrite(LED_BUILTIN, LOW);
  delay(10);
  for (int i = 0; i < 4; i++)
  {
    lcd.noBacklight();
    digitalWrite(LED_BUILTIN, HIGH);
    delay(100);
    lcd.backlight();
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
  }
  // now = millis();
  lcd.clear();
}

void printToLCD(float value, byte width, byte dp) {
  char out[10];
  dtostrf(value, width, dp, out);
  lcd.print(out);
}

void loop()
{

  //initialise variables and reset loop totals to 0
  unsigned long temp1Total = 0; // temp1 darlington
  unsigned long temp2Total = 0; // temp2 heatsink
  unsigned long voltageTotal = 0;
  unsigned long currentTotal = 0;
  unsigned int signalMax0 = 0; // max value from analog 0
  unsigned long signalMin0 = 150000; // min value from analog 0, temp1 Darlington
  unsigned int signalMax1 = 0; // max value from analog 1
  unsigned long signalMin1 = 150000; // min value from analog 1, temp2 heatsink
  unsigned int signalMax2 = 0; // max value from analog 2
  unsigned long signalMin2 = 450000; // min value from analog 2, output voltage


  //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 / 1023); // darlington temp read m°C LM35 = 10mV/°C, 5V = 500000 m°C
    delayMicroseconds(50);
    // temp1Value = (analogRead(0) * 50000 / 1023);
    // delayMicroseconds(1000);
    temp2Value = (analogRead(1) * 50000 / 1023); // heatsink temp read m°C LM35 = 10mV/°C
    delayMicroseconds(50);
    // temp2Value = (analogRead(1) * 50000 / 1023);
    // delayMicroseconds(1000);
    // voltageValue = (analogRead(2) * 45000 / 1024); // mV
    // voltageValue = (analogRead(2) * 45000 / 1024); // mV
    // delayMicroseconds(1000);
    // voltageValue = (analogRead(2) * 45000 / 1023); // mV
    // delayMicroseconds(10);
    voltageValue = (analogRead(2) * 45000 / 1023); // mV
    // Serial.print(F(" Darl. temp. = "));
    // Serial.print(temp1Value);
    // Serial.print(F("  heatsink temp. = "));
    // Serial.print(temp2Value);
    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*(x+2)
    temp2Total += temp2Value; // temp2 cooling fins accum m°C*(x+2)
    voltageTotal += voltageValue; // spanning mV*(x+2)
    currentTotal += currentValue; // stroom mA*(x+2)

    //test conditions for overtemp and or overcurrent warnings
    if (voltageValue / 1000 <= v1 && currentValue / 1000 >= C1) // shortcircuit
    {
      OC = true;
      lcd.setCursor (0, 1);
      lcd.print(F("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;
  // Serial.print(F("   raw voltageTotal = "));
  // Serial.print(voltageTotal);
  voltageTotal -= signalMin2; // output voltage mV
  voltageTotal -= signalMax2;
  // Serial.print(F("   signalMin2 = "));
  // Serial.print(signalMin2);
  // Serial.print(F("   signalMax2 = "));
  // Serial.print(signalMax2);
  // Serial.print(F("   voltageTotal = "));
  // Serial.print(voltageTotal);
  // Serial.print(F("  Darl. min. = "));
  // Serial.print(signalMin0);
  // Serial.print(F("  Darl. max. = "));
  // Serial.print(signalMax0);
  // Serial.print(F("  heats. min. = "));
  // Serial.print(signalMin1);
  // Serial.print(F("  heats. max. = "));
  // Serial.print(signalMax1);
  // Serial.print(F(" Darl.Temp.Ttl = "));
  // Serial.print(temp1Total);
  // Serial.print(F(" Heatsink.Temp.Ttl = "));
  // Serial.println(temp2Total);

  //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; // x = number of actually used measurements
  currentDisplay = currentTotal / 1000.0 / (x + 2);  // x+2 = number of actually used measurements
  // Serial.print(F("   voltageDisplay = "));
  // Serial.println(voltageDisplay);

  cumulVoltage += voltageTotal;
  cumulCurrent += currentTotal;
  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
    }
  }

  if (millis() - now1 >= interval) // execute every interval1 milliseconds (1000)
  {
    //test for overtemp conditions
    if ((temp1Value >= T1 && temp1Value < T2 && OC == false) || (temp2Value >= T1 && temp2Value < T2 && OC == false))  // warning high temp darlington or heatsink, no shortcircuit
    {
      // Serial.println(F("  high temp conditions"));
      // Serial.print(F("  darl = "));
      // Serial.print(temp1Value);
      // Serial.print(F("  HS. = "));
      // Serial.println(temp2Value);
      T1 = T1a; // hysteresis set to value 0°C: T2
      // T2b = T2a; // hysteresis set to value 2C: T2 -2°C
      warning = true;
      lcd.setCursor (0, 1); // go to start of 2nd line

      if (message1 == true)   // alarm, after second passage after interval3
      {
        message1 = false;
        interval = interval1;     // faster interval
        lcd.print(F("HIGH TEMP WARN!!"));
        digitalWrite(LED_BUILTIN, HIGH);
      }
      else                    // alarm reset, or first passage after high temp detect
      {
        message1 = true;
        interval = interval3;     // LCD regular refresh
        digitalWrite(LED_BUILTIN, LOW);
        lcd.print(F("I="));
        printToLCD(currentDisplay, 5, 2);
        lcd.write(F("A"));
        lcd.print(F(" T2="));
        printToLCD(temp2Value, 3, 0); // heatsink temp on line 1
        lcd.print(F("C"));
      }
    }

    if ((temp1Value >= T2 && OC == false) || (temp2Value >= T2 && OC == false))  // alarm overtemperature darlington or heatsink, no shortcircuit
    {
      // Serial.println(F("  overtemp conditions"));
      // Serial.print(F("  darl = "));
      // Serial.print(temp1Value);
      // Serial.print(F("  HS = "));
      // Serial.println(temp2Value);
      // T2b = T2a; // hysteresis st -2°C
      lcd.setCursor (0, 1); // go to start of 2nd line
      warning = true;
      digitalWrite(RELAIS, HIGH);         // overtemperature, relay opens
      lcd.setCursor (0, 1); // go to start of 2nd line

      if (message2 == true)   // alarm, after second passage after interval3
      {
        message2 = false;
        interval = interval2;     // faster interval
        lcd.print("OVERTEMP WARNING");
        digitalWrite(LED_BUILTIN, HIGH);
        digitalWrite(RELAIS, HIGH);         // overtemperature, relay opens
        digitalWrite(BEEP, HIGH);           // overtemperature, beep sounds
        tone(BEEP, 4000);
      }
      else                    // alarm reset, or first passage after high temp detect
      {
        message2 = true;
        interval = interval3;     // LCD regular refresh
        digitalWrite(LED_BUILTIN, LOW);
        digitalWrite(BEEP, LOW);
        lcd.print(F("I="));
        printToLCD(currentDisplay, 5, 2);
        lcd.print(F("A"));
        lcd.print(F(" T2="));
        printToLCD(temp2Value, 3, 0); // heatsink temp on line 2
        lcd.print(F("C"));
      }
    }
    now1 = millis();
  }

  if (millis() - now2 >= interval3)  // execute every interval1 milliseconds (500)
  {
    voltageDisplay = cumulVoltage / cumul / 1000.0 / x;
    currentDisplay = cumulCurrent / cumul / 1000.0 / (x + 2);
    // Serial.print(F("   voltageDisplay = "));
    // Serial.print(voltageDisplay);
    // Serial.print(F("  cumul = "));
    // Serial.println(cumul);
    cumulVoltage = 0;
    cumulCurrent = 0;
    cumul = 0;

    lcd.home ();
    lcd.print(F("U="));
    printToLCD(voltageDisplay, 5, 2);
    lcd.print(F("V"));
    lcd.print(F(" T1="));
    printToLCD(temp1Value, 3, 0); // Darlington temp on line 1
    lcd.print(F("C"));
    lcd.setCursor (0, 1); // go to start of 2nd line

    if (warning == false && OC == false)
    {
      digitalWrite(LED_BUILTIN, LOW);
      lcd.print(F("I="));
      printToLCD(currentDisplay, 5, 2);
      lcd.print(F("A"));
      lcd.print(" T2=");
      printToLCD(temp2Value, 3, 0); // heatsink temp on line 2
      lcd.print(F("C"));
      //}
      now2 = millis();
    }
  }

  if (temp1Value < T1 && temp2Value < T1 && OC == false)   // temperatures back to normal, no short circuit, protection relay and beep reset
  {
    lcd.setCursor (0, 1);
    noTone(BEEP);  // reset buzzer
    digitalWrite(RELAIS, LOW);
    digitalWrite(BEEP, LOW);           // overtemperature, beep sounds
    digitalWrite(LED_BUILTIN, LOW);
    warning = false;
    T1 = T1b; // reset T1 hysteresis to 0°C
  }
}

which arduino are you using?

do another try with two (or three) lines.

P.S. this line is still missing the F-Makro

 lcd.print(" T2=");

or this one

lcd.print("OVERTEMP WARNING");

furthermore, don't duplicate code when you are low on memory. Put similar things in a reusable function and call the function.

@noiasca ..thanks, will do.

@noiasca I am trying to find this duplicate code must I must be blinded, can you please give a hunch?