Serial.print usage produces too large sketch

Hello everybody!

I encountered a problem with this sketch:

#include <SendOnlySoftwareSerial.h>
#include <math.h>

SendOnlySoftwareSerial mySerial(1);  // Tx pin

void setup ()
{
  mySerial.begin(4800);
  pinMode(0, INPUT);
  pinMode(4, INPUT);
}

void loop ()
{
  int sensorValueA1 = analogRead(A1); 
  int R10k_ntc = 9870;
  float U10k_ntc, Untc, Rntc, Temp;
  
  U10k_ntc = sensorValueA1*(5.0/1024.0); 
  Untc = 5.0 - U10k_ntc;
  Rntc = (R10k_ntc*Untc)/U10k_ntc; 
  Temp = (298.15/(1-(298.15/4300.0)*log(10000.0/Rntc)))-(273.15+0); 
  
  mySerial.print(millis()/1000); mySerial.print("\t"); mySerial.print(int(Temp)); mySerial.print("\n");

  delay(1000); 
}

The program runs on an ATtiny45 and simply transmits the temperature derived from an NTC via software emulated UART.
So far, it works but the size of the sketch is 4052 Bytes and this is too big as this is not the only thing the ATtiny is supposed to do.
As it seems, the core of the problem is the "mySerial.print(int(Temp))" function call.
For test purposes, I have replaced "int(Temp)" by "sensorValueA1", which is also an integer data type. Then, the sketch size is 2.574 Bytes. This would be perfectly acceptable.

Has anybody an idea what is going on here?

Thanks in advance

AK

You might want to look into how the int() "function" is implemented. You might want to think about using a cast, instead.

mySerial.print((int)Temp);

Thanks for the quick answer.
Forgot to mention that type casting instead of using int() has no effect on the size. After compilation it has still 4.052 Bytes.

AK

So, how about adding an intermediate variable?

int tempAsInt = Temp;
mySerial.print(tempAsInt);

Hello PaulS!!

Without introducing any temporary variable i found size of code is 5602 Bytes.
IDE i am using is Arduino 1.0.5

With Temporary variable(that you suggested) size of code: 5586 Bytes

so i also want to know why size reduced just of introducing a temporary variable...
thank you

In my case, it still results in 4.052 Bytes.
Perhaps this helps:

Replacing this line:

Rntc = (R10k_ntc*Untc)/U10k_ntc;

by:

Rntc = 1.23456;      // float for testing

results in a 2.566 Bytes sketch.

As Rntc is used in the calculation of Temp, and Temp is used in int(), it seems that the origin of the problem lies there. But I do not have a clue.

AK

It seems the arithmetic operators on data type float have to be emulated so you are compiling in the whole emulation library?

I do not know exactly what you mean, but why is the code significantly smaller when I replace the float variables in

Rntc = (R10k_ntc*Untc)/U10k_ntc;

by floating point numbers like 1.2345?

My biggest problem is to locate the origin of the problem. It is either in the obove mentioned line of code, as the substitution of this line results in smaller code, or it is in the line with

mySerial.print(int(Temp))

as the substitution of int(Temp) by "some chars" results in smaller code, too.

I never realized this problem when using an Arduino board, as these have enough memory, but there has to be some solution for the Tiny45.

as the substitution of int(Temp) by "some chars" results in smaller code, too.

When you quit printing Temp, the compiler sees that it is not necessary to compute it, so all the floating point arithmetic code is not linked in, resulting in a much smaller sketch.

but there has to be some solution for the Tiny45.

Why do you believe that?

If you are short of space, why not just send the analog reading (sensorValueA1) via serial print, and let the receiver do all that maths? Doing the log alone uses about 300 bytes. These chips don't have floating-point hardware so doing any sort of floating-point arithmetic is going to add to the program size somewhat.

I finally ended up with doing exactly this. But it is not an ideal solution as the temperature calculation within the controller offered the possibility to correct offsets given by the tolerances of the NTCs. By transmitting only the raw ADC data, the software at the PC has to be aware of the offsets of all transmitting devices.

Anyhow, I would like to understand this. I encountered, that compiling the original code for an Arduino Uno with both sending and not sending the temperature caused a difference of only 6 Bytes regarding the final sketch size. Compiling it for an ATtiny45 results in a vastly greater size difference.

Edit: Compiling it for Arduino UNO does not change things.

AK

@ PaulS:

I think you are perfectly right.

I took some other code, where I was printing the temperature to a LCD and transmitting it via UART.
If I switch off the data transmision, the code only shrinks a little bit. But switching off the LCD output, too, the sketch size is about 1900 Bytes smaller.
This strongly supports your comment on the compiler behaviour.

Thanks to all of you for your help!

AK

This strongly supports your comment on the compiler behaviour.

You can use objdump to dump the object files, to see what is actually happening. Using your original sketch, dump the object file. Then, change the print() statement to print a hardcoded value, instead of Temp. Compile and use objdump again. Notice how much code is NOT in the object file - nothing to do with floating point calculations, because the compiler can see that the result is never actually used, so there is no real need to perform the calculation.

This is really interesting. Until now, I never gave thought to this because when writing C programs for personal computers you rarely hit the memory limit.

That means, when I replace (for example) the expression

Rntc = (R10k_ntc*Untc)/U10k_ntc;

by

Rntc = 1.234;   // some arbitrary value

the calculation of Temp will be performed as long as I print it, but the compiler "realizes" that the following code

int sensorValueA1 = analogRead(A1); 
  int R10k_ntc = 9870;
  float U10k_ntc, Untc, Rntc, Temp;
  
  U10k_ntc = sensorValueA1*(5.0/1024.0); 
  Untc = 5.0 - U10k_ntc;

is not necessary any more for the determination of Rntc and will omit it during compilation?

Is this right?

Sure, the compiler is smart. For a Uno, try this:

int main ()
{
}

Size:

Binary sketch size: 176 bytes (of a 32,256 byte maximum)

Now add all your calculations:

int main ()
{
  int sensorValueA1 = 42; 
  int R10k_ntc = 9870;
  float U10k_ntc, Untc, Rntc, Temp;
  
  U10k_ntc = sensorValueA1*(5.0/1024.0); 
  Untc = 5.0 - U10k_ntc;
  Rntc = (R10k_ntc*Untc)/U10k_ntc; 
  Temp = (298.15/(1-(298.15/4300.0)*log(10000.0/Rntc)))-(273.15+0);  
}

Size:

Binary sketch size: 176 bytes (of a 32,256 byte maximum)

The compiler has thrown the whole lot away! It knows you don't need it.

Now print the result:

int main ()
{
  int sensorValueA1 = 42; 
  int R10k_ntc = 9870;
  float U10k_ntc, Untc, Rntc, Temp;
  
  U10k_ntc = sensorValueA1*(5.0/1024.0); 
  Untc = 5.0 - U10k_ntc;
  Rntc = (R10k_ntc*Untc)/U10k_ntc; 
  Temp = (298.15/(1-(298.15/4300.0)*log(10000.0/Rntc)))-(273.15+0);
  Serial.println (Temp); 
}

Size:

Binary sketch size: 3,366 bytes (of a 32,256 byte maximum)

What a difference one line makes, eh?

That is quite interesting -- a day ago I thought that during compilation/linking everything from the source is going to be transformed into the binary program.

But there is always a lot to learn.
Thank you very much for these insights!

P.S.: I am aware of the fact that using the Arduino software platform for programming a Tiny45 might not be the best choice from the professional programmer's point of view. But as I am a chemist and try to monitor a -150 °C nitrogen gas stream for cooling ammonia rich crystals on a X-ray diffractometer, the whole platform offers me the possibility to build and program little environmental sensors. This is great. Thanks a lot guys!

The platform's fine. It saves space in a couple of ways. For one thing, the compiler aggressively optimizes away code that isn't needed. Second, the linker throws away functions that aren't called. (For example, if you use the math library and don't call log() then the code for that is not included).

However if you are running out of memory why not get a slightly larger chip? For example the Atmega328P? After all, it's only $5 and I bet that is a small part of your overall budget. Saves a lot of mucking around with the smaller chip.

Slightly larger? For 4¢ less than the price of an ATtiny45, @akkorber can get a processor with twice the memory. (In the U.S. ATtiny85s are sometimes cheaper than ATtiny45s. Rarely are they different by more than 20¢.)

Well, I meant "somewhat larger".

Checking on DigiKey, the ATMEGA328P-PU (28DIP) is selling in single quantities for $3.74.

Compare to the ATTINY85-20PU for $1.69 and the ATTINY45-20PU for $1.49.

So for $2.25 more you upgrade from 4 kB of flash to 32 kB of flash.

hecking on DigiKey, the ATMEGA328P-PU (28DIP) is selling in single quantities for $3.74.

But for $2.49 U.S.D. (qty 10 free shipping) you can get a 5V 16MHz Pro Mini:
http://www.ebay.com/itm/10PCS-Pro-Mini-atmega328-5V-16M-Replace-ATmega128-Arduino-Compatible-Nano-/201157120362

I have a box of 30+ of these things... They just work and if not, re-flash the boot loader! In 20 I have tested, 2 had to be reflashed.

Ray