## math.h accos and linker error

4 posts / 0 new
Author
Message

Good day,
Just about finished a project and got to do the floating point stuff now, but stuck on a snag with math.h,

my function is to turn a level into a volume for a circular water tank:

/** @brief      Hardware abstraction for volume
@details    Using the level() function to determine the volume based on the
profile of the tank.
Based on a piecwise defined function for (I) level < radius and
(II) level >= radius. with
theta = (I) 2*acos( (r-h)/r) , (II) 2*acos( (2r-h)/r )
area = (I) r^2(theta - sin(theta))/2, (II) pi*r^2-r^2(theta - sin(theta))/2
@param      Level in mm
@retval     water_volume in milli litres
*/
uint16_t volume( uint16_t level ){
double theta;
double volume;
if ( level >= (TANK_RADIUS/2) ) {
theta = 2*acos( (TANK_RADIUS - level )/TANK_RADIUS);
volume = TANK_LENGTH*( TANK_RADIUS*TANK_RADIUS*(theta - sin(theta))/2);
}
else if (level <= TANK_RADIUS) {
theta = 2*acos( (2*TANK_RADIUS - level )/TANK_RADIUS);
volume = TANK_LENGTH*(TANK_RADIUS*TANK_RADIUS*(M_PI - (theta - sin(theta))/2));
}
else { //level is greater than what it should be so return NOTHING or ... max!
volume = TANK_LENGTH*TANK_RADIUS*TANK_RADIUS*M_PI;
}
return (uint16_t)(volume+0.5);
}

with makefile flags and stuff:

CFLAGS = -c -g -Os -Wall -w -ffunction-sections -fdata-sections -mmcu=\$(MCU) \
-DF_CPU=\$(F_CPU)L
CPPFLAGS = \$(CFLAGS) -fno-exceptions
LDFLAGS = -Os -Wl,--gc-sections -mmcu=\$(MCU) -lm
HEXFLAGS = -O ihex -R .eeprom

gives this error on compilation:

linking .build/project.elf (gcc) /usr/lib/gcc/avr/4.7.2/../../../avr/lib/avr5/libc.a(fp_arccos.o):../../../libm/fplib/fp_arccos.S:76: relocation truncated to fit: R_AVR_13_PCREL against symbol `__subsf3' defined in .text section in /usr/lib/gcc/avr/4.7.2/avr5/libgcc.a(_addsub_sf.o)
/usr/lib/gcc/avr/4.7.2/../../../avr/lib/avr5/libc.a(fp_powsodd.o):../../../libm/fplib/fp_powsodd.S:59: relocation truncated to fit: R_AVR_13_PCREL against symbol `__mulsf3' defined in .text section in /usr/lib/gcc/avr/4.7.2/avr5/libgcc.a(_mul_sf.o)
/usr/lib/gcc/avr/4.7.2/../../../avr/lib/avr5/libc.a(fp_powsodd.o):../../../libm/fplib/fp_powsodd.S:69: relocation truncated to fit: R_AVR_13_PCREL against symbol `__mulsf3' defined in .text section in /usr/lib/gcc/avr/4.7.2/avr5/libgcc.a(_mul_sf.o)
collect2: error: ld returned 1 exit status
make: *** [.build/project.elf] Error 1
*** Failure: Exit code 2 ***

The fault can be replicated with the smallest snippet being:

accos(1.1)

So clearly is related to the compiler/linker believing that the argument to accos() will not be in the domain [-1,1].
In my code I am fairly certain this will always be so.

What can I do?

Will my returned value really be a uint16_t truncation of the floating point value. IS there a more efficient way.

As a corollary, is it more efficient to use an analytic formula for volume, or would it be more efficient to do numeric integration considering at most it would be 500 iterations of a function involving square roots and squares.

Thanks all
Jasper

Total votes: 0

Your -lm is surely in the wrong place? It must come at the very end of the command after the other link objects have been listed.

Change the actual link rule (where LDFLAGS is being used) and put it on the end.

Total votes: 0

Thanks Clawson,
I think this has done the trick.

For the reference of others, (some of) my Makefile now looks like:

CFLAGS = -c -g -Os -Wall -w -ffunction-sections -fdata-sections -mmcu=\$(MCU) \
-DF_CPU=\$(F_CPU)L
CPPFLAGS = \$(CFLAGS) -fno-exceptions
LDFLAGS = -Os -Wl,--gc-sections -mmcu=\$(MCU) -lm
HEXFLAGS = -O ihex -R .eeprom
...
...
...
# linking the program
\$(WORKDIR)/\$(TARGET).elf: \$(PROG_OBJ)
\$(CC) \$(PROG_OBJ) -o \$@ \$(LDFLAGS)

Is there any better practice method of converting double into uint16_t than:

return (uint16_t)(some_double + 0.5);
Total votes: 0

Quote:

my Makefile now looks like

If you want to know what a "proper" Makefile for avr-gcc looks like then examine the Mfile template. It has already faced all these issues and solved them.
Quote:

Is there any better practice method

Why? What do you see wrong with the cast? (just out of interest if the interface to the function declared a uint16_t return wouldn't there just be an implicit type conversion anyway?) (*)

BTW you do know that "double" doesn't really exist in avr-gcc in that both double and float are both 32 bit so are effectively identical - if you thought you were going to get 64 bit precision by using double you won't.

EDIT: (*) yup thought so:

uint16_t test(void) {
double d;
d = ADC * 3.141;
return d;  //implicit type conversion
}

int main(void)
{
PORTB = test();
while(1) {
}
}

That builds without warning. The code generated is:
test:

//==> uint16_t test(void) {
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
//==> 	d = ADC * 3.141;
in r22,0x4
in r23,0x4+1
ldi r24,0
ldi r25,0
call __floatunsisf
ldi r18,lo8(37)
ldi r19,lo8(6)
ldi r20,lo8(73)
ldi r21,lo8(64)
call __mulsf3
//==> 	return d;
call __fixunssfsi
//==> }
movw r24,r22
ret

The last call (__fixunssfsi()) is the function that does float to int conversion.

I'd leave the "//implicit type conversion" comment in there as a reminder to the maintainer that what's happening is deliberate.