Floating Point Math on Mega128

Go To Last Post
20 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wrote this code in CodeVision for a Mega128, but when I attempt to print it, all I get is "2f". Let's assume all the math conversions are correct, I've triple-checked them in Excel and on my calculator. This code also runs fine on x86. Is this simply a matter of the Mega128 choking on floating point math, or am I (as usual) doing something dumb?

 

unsigned char btemp[4];        
btemp[0] = 0x5D;        
btemp[1] = 0xA5;
btemp[2] = 0x1A;
btemp[3] = '\0';        
 
float btempf;
float vbtemp;
float rbtemp;
float tbtemp;
 
//bitwise op to create decimal rep of hex char array 
btempf = (btemp[0] << 16) | (btemp[1] << 8) | btemp[2];

//convert from counts to volts, to resistance, to temperature
vbtemp = (4.096 / 16777216) * btempf;       
rbtemp = 11.501 * vbtemp * vbtemp - 160.12 * vbtemp + 1302.8;
tbtemp = 0.2564 * rbtemp - 256.19;
printf("%.2f", tbtemp);  // Print out temperature

 

This topic has a solution.
Last Edited: Wed. Sep 12, 2018 - 10:54 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Set the full-fat printf() in the Project Configuration.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

That was it, thanks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

... if you want to save some program memory and only want to use some printf-features, you may want to write your own function.

 

For example something like this:

 

void snprint_float(char* StrBuffer, size_t size, float fNumber)
{
   int iDeci;
   int iUnit;
   int iFrac;
   int iSign = 1;
   if (fNumber < 0)
   {
      fNumber = -fNumber;
      iSign = -1;
   }
   iDeci = (int)(fNumber * 100.0f + 0.5f); // round
   iUnit = (int)fNumber;
   iFrac = iDeci - iUnit * 100;
   snprintf(StrBuffer, size, "%d.%02d", iSign * iUnit, iFrac);
}

 

But in special cases (inf, rounding, ...) it will behave differently.

In the beginning was the Word, and the Word was with God, and the Word was God.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

(4.096 / 16777216)  is quite a small number....will that be well represented by your floating point, or truncated?

 

May be better to mult first, then divide.  Of course, perhaps, the compiler may surprise you by its agility.

 

(4.096*btempf) / 16777216

 

Of course you have multiple calcs all chained together piecemeal...could be betterto do that yourself (or convince the compiler to do it).

 

ex:  a= b+2*c

d=a*a+4.................is really d=b*b+4*(c*(a+c)+1)

 

so, your chain calc is really: 

 

answer= 77.84792-0.0000100231*bt +1.75766e-13 * bt *bt

 

This should be considered carefully!   1 million gives an answer of 68.000566

 

 

    

 

 

 

 

 

When in the dark remember-the future looks brighter than ever.

Last Edited: Thu. Sep 13, 2018 - 02:58 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrcandies wrote:
(4.096 / 16777216)  is quite a small number....will that be well represented by your floating point, or truncated?
Why is the fact that it's a small fraction anything to do with the accuracy with which it is stored? All float are stored in 24 bits (23 plus the normalised 1 at the front).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If the OP is printing temperature to 2 decimal places you are not talking about massive precision.

 

It seems quite straightforward.    He makes a f-p calculation.   And wants a human-readable result.   (which generally means rounding)

printf() does the job nicely.    The OP can get on with the rest of his project.

 

Humans read fairly slowly.    I doubt if the calculation time is important.

AVRs have plenty of Flash.     If he is tight for Flash memory,   the OP will ask specifically about reducing program size.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
If the OP is printing temperature to 2 decimal places you are not talking about massive precision.

Mentioned was volts/resistance/temperature.

 

Coming from an AVR8 10-bit reading, there are only 1000 possible values anyway.  Given tolerance in the readings, some fraction of that.  Yes, on a few occasions I've done a floating point conversion.  But why fuss with it?  Do the calculations in e.g. millivolts/ohms/centidegrees.  Then show the whole and partial counts like skotti showed -- without any floating point at all.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My argument is always:   Write straightforward code that is easy to maintain.

 

If God gave you a proven library function like printf(),   use it.

 

Only worry about size or efficiency if it is a problem e.g.  won't fit in your target chip.

 

You can calculate from a published algorithm using f-p maths.   The accuracy will be more than needed (from a 10-bit ADC).

But the most important point is you don't worry about "intermediate overflows or underflows".

 

Yes,  theusch's advice to do integer maths in milliVolts is very useful.    But you can soon go wrong with int16_t maths.    If you come from ARM or PC you will be used to int32_t maths (which is unlikely to overflow or underflow).

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How can anyone question any advice/recommendation from the esteemed David?  If that would happen, what would be next -- impugning Mother and Apple Pie?

 

Remember that I am pretty ancient.  So to me a couple buck microcontroller has limited resources.  If I had followed David's approach over the past 20 years, I'd only have been able to design-in "big iron" AVR8 models, and Mega48/88 and 8-pin Tiny full apps would have only been a pipe dream.  I forget which 'Freak a millennium or so ago advocated "start with a Mega32 and use that throughout development so you don't run out of flash on the first day".

 

But I guess when large and powerful CortexM and other micros can be had for a buck or two, us penny-watchers are now passe.  When will I learn "penny wise, pound foolish"?

 

So, if I would care to further this

david.prentice wrote:
But you can soon go wrong with int16_t maths.

...if you are indeed going to do microcontroller work and cannot get over the learning curve of C arithmetic rules then I'll opine that you are in the wrong profession.

 

There--now I can retire in peace, not having left the above unsaid.  My summary:  "Apparently there is no such thing as a full app on a Mega48 anymore."

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lee,

 

Surely we can agree to disagree.

 

I started with a AT90S2313.    You certainly had to be careful with memory.

 

I then got a ATmega32 dev board.     Plenty of Flash, SRAM, peripherals, ...

It only becomes "tight" when you have lots of Fonts, Graphics, Images, ...

 

Nowadays,   people start with a 32kB Uno or Nano.      Which is a very comfortable size of chip.

I still maintain that it is easier to develop ideas on a well endowed chip.   When debugged,   you can rebuild for the smallest, cheapest target.     There is nothing wrong with a Mega48 or Mega88 application.

 

Yes,   it is good exercise to squeeze into a 2kB or 4kB  chip.    Pointless from a commercial point of view.    Satisfying from an academic point of view.

 

People come to the Forum with questions about their projects.

I am sure that it is more productive to start with straightforward code and proven libraries.

 

It is better to tweak working code than to start with something that might not work.     Or worse still,   something that works for only part of your range of values.

 

You still learn about arithmetic and overflows when you start the tweaking process.

 

David.

 

p.s. the thread title was about a 128kB chip.   Plenty of memory.    None of the "memory access" features that bite a mega2560 user.

Last Edited: Thu. Sep 13, 2018 - 03:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
Pointless from a commercial point of view.

???  "Pointless" nowadays?  Or are you saying that all of our production '4433 and '2313 and Mega48 and Tiny25 and ... apps over the years are pointless?  I've been working in the wrong profession, apparently.

 

I disagree with the "We'll make it smaller later" approach.  "Code expands to fit the space available."

 

While the 2K/4K debate may well be passe, isn't it just important nowadays to be able to use a $1 micro vs. a $2 model?

 

All that said, on a one-off with materials at hand then yeah, it doesn't matter much.  Who started this, anyway?

 

skotti wrote:
... if you want to save some program memory and only want to use some printf-features, you may want to write your own function.

Given the tone of the question, indeed I'd agree that OP is not close to filling up the Meg128 or running out of cycles.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Of course your historic apps are not pointless.     But I doubt that you would choose 4433 for a new design.

 

There is not much difference in price for ATmega48PB .. ATmega168PB.    Even ATmega328PB is not far off.

 

Yes,   of course $0.03 makes a difference to a 10000 unit run.    Less significant for lower production runs.

How many hours of your time do you get for $300 ?

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

lavadisco wrote:
//convert from counts to volts, to resistance, to temperature vbtemp = (4.096 / 16777216) * btempf; rbtemp = 11.501 * vbtemp * vbtemp - 160.12 * vbtemp + 1302.8; tbtemp = 0.2564 * rbtemp - 256.19; printf("%.2f", tbtemp); // Print out temperature

Steinhardt-Hart for temperature from NTC thermistor?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
But the most important point is you don't worry about "intermediate overflows or underflows".
(the following is OT as it's AVR GCC instead of CodeVisionAVR)

That's an advantage as AVR GCC fixed-point arithmetic is non-saturating; saturation is optional in Embedded C.

david.prentice wrote:
If you come from ARM or PC you will be used to int32_t maths (which is unlikely to overflow or underflow).
That's handy.

For one application I worked, the overflows and such were detected on a 32b MPU with an OS and floating-point arithmetic then the corrected application was ported to a 16b MPU bare board in fixed-point arithmetic (16b MPU constraints: some memory space, significant time given MPU's clock and allocated duration per the system)

fyi, C11 has Language Independent Arithmetic (LIA) (Annex H) which adds integer_overflow.

C11 is in GCC and has integer overflow; Microsoft Visual C++ has C89.

 


https://gcc.gnu.org/wiki/avr-gcc#Fixed-Point_Support

TR 18037: Embedded C

http://www.open-std.org/JTC1/SC22/WG14/www/projects#18037

(in N1169, search for _Sat)

http://www.open-std.org/JTC1/SC22/WG14/ (C)

https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Standards.html#C-Language

https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Integer-Overflow-Builtins.html

https://docs.microsoft.com/en-us/cpp/c-language/c-language-reference?view=vs-2017

 

Edits: constraints, Visual C++

 

"Dare to be naïve." - Buckminster Fuller

Last Edited: Thu. Sep 13, 2018 - 09:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for all the advice, everyone.

 

To follow up: Yes, I can print floats now, but in order to keep code size down, in the end I decided to use the slim printf and try to output a padded integer instead. And the number I get is incorrect. The weird thing is this code generates the correct number on x86. Is there something about Codevision that would prevent the correct number being calculated?

 

unsigned char btemp[4];        
btemp[0] = 0x5D;        
btemp[1] = 0xA5;
btemp[2] = 0x1A;
btemp[3] = '\0';        
 
float btempf;
float vbtemp;
float rbtemp;
float tbtemp;
float btempout;
int btempouti;

btempf = (btemp[0] << 16) | (btemp[1] << 8) | btemp[2]; //bitwise op to create decimal rep of hex char array
vbtemp = (4.096 / 16777216) * btempf;       // Calculate temp in deg C
rbtemp = 11.501 * vbtemp * vbtemp - 160.12 * vbtemp + 1302.8;
tbtemp = 0.2564 * rbtemp - 256.19;
btempout = tbtemp*100;      // Multiply temperature by 100
btempouti = btempout;       // Shove temperature float into int - is this legit?
printf("board temp: %d\n", btempouti);  // Print int instead of float

 

And avrcandies' point had occurred to me,  re: compact equations vs piece-wise equations, but it doesn't really seem like that would make any difference?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

lavadisco wrote:
And the number I get is incorrect. The weird thing is this code generates the correct number on x86.
A linter is producing a lot of

error 18: symbol '<symbol name>' redeclared ...

https://www.gimpel.com/demo.html

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And avrcandies' point had occurred to me,  re: compact equations vs piece-wise equations, but it doesn't really seem like that would make any difference?

Many decimal numbers have no exact binary equivalent ,so a bit of rounding/truncation can occur & of course build up to a worsening state after repeated blending of such types of  values.  In some cases, the compiler might be smart enough to figure out that linked equations can be simplified together, but I'd be surprised if it always did.

When in the dark remember-the future looks brighter than ever.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Some examples of the 'correct' and 'incorrect' values would be useful.
The main difference between the code in post #1 and post #16 is that you are multiplying by 100 and that will cause problems when tbtemp exceeds 327.68
You can typecast and do something like printf( "board temp: %u\n", (unsigned int) ( btempout * 100 ) );

Edited to fix 'off-by-a-factor-of-10' error

Last Edited: Fri. Sep 14, 2018 - 07:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your basic problem is down to your parsing of the char[] array into a float.

It illustrates the problems that you can get with an int16_t compiler.

 

I strongly recommend that you parse a float by multiplying each char with its weight.

You can use shifts if you want,   but you must understand how a compiler evaluates expressions.

With an int32_t compiler the uint8_t "char" will be used in expressions with little fear of hitting the sign bit.

 

Incidentally,   I have no knowledge of your algorithm.    It seems to calculate very high temperatures.

 

Try this on an Arduino:

void show_long(int32_t val, char *name)
{
    Serial.print(name);
    Serial.print(" (0x");
    Serial.print(val, HEX);
    Serial.print(") [");
    Serial.print(val, DEC);
    Serial.println("]");
}

void setup()
{
    unsigned char btemp[4];
    float btempf;
    float vbtemp;
    float rbtemp;
    float tbtemp;
    float btempout;
    int btempouti;
    int32_t long_1, long_2, long_3;
    float float_mult;

    btemp[0] = 0x5D;
    btemp[1] = 0xA5;
    btemp[2] = 0x1A;
    btemp[3] = '\0';

    Serial.begin(9600);
    Serial.print("Hello Arduino @ 9600 baud on Uno\r\n");

    long_1 = (btemp[0] << 16) | (btemp[1] << 8) | btemp[2];        // no casts
    long_2 = ((uint32_t)btemp[0] << 16) | (btemp[1] << 8) | btemp[2];  // I would expect only one cast is reqd
    long_3 = ((uint32_t)btemp[0] << 16) | ((uint32_t)btemp[1] << 8) | btemp[2];  //since bit7 of [1] set shifts into sign
    float_mult = 65536.0 * btemp[0] + 256.0 * btemp[1] + btemp[2]; //MULTIPLY to create decimal rep of hex char array
    btempf = (btemp[0] << 16) | (btemp[1] << 8) | btemp[2]; //bitwise op to create decimal rep of hex char array
    show_long(long_1, "long_1");
    show_long(long_2, "long_2");
    show_long(long_3, "long_3");
    Serial.print("float_mult ");
    Serial.println(float_mult, 3);
    Serial.print("btempf ");
    Serial.println(btempf, 3);
}

void loop()
{
}

Or this CodeVision program:

#include <io.h>
#include <stdio.h>
#include <stdint.h>

void main(void)
{
	unsigned char btemp[4];
	float btempf;
	float vbtemp;
	float rbtemp;
	float tbtemp;
	float btempout;
	int btempouti;
	int32_t long_1, long_2, long_3;
	float float_mult;

	btemp[0] = 0x5D;
	btemp[1] = 0xA5;
	btemp[2] = 0x1A;
	btemp[3] = '\0';

	UCSR0B = (1 << RXEN0) | (1 << TXEN0);
	UBRR0L = 103;    //F_CPU/16/9600 - 1
	printf("Hello Codevision @ 9600 baud on Uno\r\n");

	long_1 = (btemp[0] << 16) | (btemp[1] << 8) | btemp[2];        // no casts
	long_2 = ((uint32_t)btemp[0] << 16) | (btemp[1] << 8) | btemp[2];  // I would expect only one cast is reqd
	long_3 = ((uint32_t)btemp[0] << 16) | ((uint32_t)btemp[1] << 8) | btemp[2];  //since bit7 of [1] set shifts into sign
	float_mult = 65536.0 * btemp[0] + 256.0 * btemp[1] + btemp[2]; //MULTIPLY to create decimal rep of hex char array
	printf("long_1 (0x%08lX) [%ld]\r\n", long_1, long_1);
	printf("long_2 (0x%08lX) [%ld]\r\n", long_2, long_2);
	printf("long_3 (0x%08lX) [%ld]\r\n", long_3, long_3);
	printf("float_mult = %f\r\n", float_mult);
	btempf = (btemp[0] << 16) | (btemp[1] << 8) | btemp[2]; //bitwise op to create decimal rep of hex char array
	vbtemp = (4.096 / 16777216) * btempf;       // Calculate temp in deg C
	rbtemp = 11.501 * vbtemp * vbtemp - 160.12 * vbtemp + 1302.8;
	tbtemp = 0.2564 * rbtemp - 256.19;
	btempout = tbtemp * 100;    // Multiply temperature by 100
	btempouti = btempout;       // Shove temperature float into int - is this legit?
	printf("btempf = %f\r\n", btempf);
	printf("vbtemp = %f\r\n", vbtemp);
	printf("rbtemp = %f\r\n", rbtemp);
	printf("tbtemp = %f\r\n", tbtemp);
	printf("board temp: %d\r\n", btempouti);  // Print int instead of float
	printf("use the correct value from float_mult (0x%08lX) [%ld]\r\n", (uint32_t)float_mult, (uint32_t)float_mult);
	btempf = float_mult;
	vbtemp = (4.096 / 16777216) * btempf;       // Calculate temp in deg C
	rbtemp = 11.501 * vbtemp * vbtemp - 160.12 * vbtemp + 1302.8;
	tbtemp = 0.2564 * rbtemp - 256.19;
	btempout = tbtemp * 100;    // Multiply temperature by 100
	btempouti = btempout;       // Shove temperature float into int - is this legit?
	printf("btempf = %f\r\n", btempf);
	printf("vbtemp = %f\r\n", vbtemp);
	printf("rbtemp = %f\r\n", rbtemp);
	printf("tbtemp = %f\r\n", tbtemp);
	printf("board temp: %d\r\n", btempouti);  // Print int instead of float
	printf("use the correct value from long_3 (0x%08lX) [%ld]\r\n", long_3, long_3);
	btempf = long_3;
	vbtemp = (4.096 / 16777216) * btempf;       // Calculate temp in deg C
	rbtemp = 11.501 * vbtemp * vbtemp - 160.12 * vbtemp + 1302.8;
	tbtemp = 0.2564 * rbtemp - 256.19;
	btempout = tbtemp * 100;    // Multiply temperature by 100
	btempouti = btempout;       // Shove temperature float into int - is this legit?
	printf("board temp: %d\r\n", btempouti);  // Print int instead of float
	while (1);
}

David.

Last Edited: Fri. Sep 14, 2018 - 09:08 AM