Moving from 8bit to 32bit

I decided that the 8bit processors are too limiting for some of my applications and have started to use the Adafruit Feather M0 that uses an ATSAMD1 processor like the Due. The benefit being that I could continue to use the Arduino IDE and, I hoped, most of the programmes I had would run with only minor adjustment. This is proving not to be quite such a smooth ride as I hoped and my latest problems is that calculations that use atan2, float variables, asin and similar math routines are generating a lot of 'nan' output on the 32bit whereas they ran just fine on the 8bit machine.

Not being an experienced C or C++ programmer I am asking for help to find the cause of this problem and help to understand why these problems are cropping up. This is the code I am using to take data from a magnetometer(compass) and from an Accelerometer to produce a tilt compensated compass Arduino-HMC5883L/HMC5883L_compensation_MPU6050.ino at master · jarzebski/Arduino-HMC5883L · GitHub

This function:
float noTiltCompensate(Vector mag)
{

  • float heading = atan2(mag.YAxis, mag.XAxis);*
  • return heading;*
    }
    takes data from the compass but returns lots of 'nan' values when the heading is printed using Serial.print on the 32bit system for very small values of the X and Y axis mag values. It works ok on the 8bit processor. If the heading fails then the remaining routines fail as well.

I know that I should probably convert everything to integers but that is something I would rather not do if I can help it.

Hope you can help me,

Peter

atan2(0,0) is undefined as it effectively is the answer to "What is the angle of a vector of zero length?"

Your 8 bit trig library is probably detecting that condition and returning '0' or some such while your 32 bit trig library is returning 'nan'.

To make the 32 bit version behave like the 8 bit version you would add a check for (Y == 0) and (X == 0) and return heading = 0 else do the atan2 and return the result of that.

Note that if X and Y are floats you would check for (abs(X) < someSmallNumber) rather than X identically zero.

Please provide specific values for atan2(x,y) that result in NaN on ARM but an actual value on AVR.

Note that atan2() and etc are functions that take and return "double" values. On an AVR, "double" and "float" are the same, but on most 32bit cpus "double" will actually use 64-bit numbers, and using arguments that are "float" will result in an extra conversion step.

Hmm. The source code for the avr atan2() function says:

[tt]/* float atan2 (float A, float B);	// A is y coord, B is x coord.
 The atan2() function calculates the arc tangent of the two variables
 A and B. It is similar to calculating the arc tangent of A/B, except
 that the signs of both arguments are used to determine the quadrant
 of the result. The atan2() function returns the result in radians,
 which is between -PI and PI (inclusive).
 
 Note:
 [color=red] This implementation returns +0.0 in all four cases: +0/+0, +0/-0,
 -0/+0 and -0/-0. Unlike x86 (GCC/Glibc).[/color]

 Algorithm:
	[color=red]if (x == 0 && y == 0)		// A is y, B is x
	 return 0[/color]
	if (x >= 0 && fabs(y) <= x)
	 return atan(y/x)		// -Pi/4 .. +Pi/4
	if (y >= 0 && fabs(x) <= y)
	 return Pi/2 - atan(x/y)	// +Pi/4 .. +3*Pi/4
	if (y <= 0 && fabs(x) <= fabs(y))
	 return -Pi/2 - atan(x/y)	// -Pi/4 .. -3*Pi/4
	if (y >= 0)
	 return Pi + atan(y/x)	// +3*Pi/4 .. Pi
	else
	 return -Pi + atan(y/x)	// -3*Pi/4 .. -Pi
 */[/tt]

Thanks for the responses.

I will try and establish the values under which I get nan values on the 32bit that are ok on the 8bit.

@westfw, I am not sure I understand the implications for " 64-bit numbers, and using arguments that are "float" will result in an extra conversion step."

Peter

I am not sure I understand the implications for " 64-bit numbers, and using arguments that are "float" will result in an extra conversion step."

I'm not sure that there are "implications", though I suppose that there could be. The 'defined' behavior of the avr-libc atan2() (not matching other implementations) is probably sufficient to explain your problem.

Thanks for the suggestions.

Further investigation shows that the problem is in the sensor library - ie further back in the chain - and that when run on a 32bit system instead of giving negative values it produces saturated positive values which are in excess of the atn2() maximum input and hence generates a nan value.

I am now trying to convert the library to work with 32bit wide registers.

Peter

Huh. Yes, one of the other differences is that sizeof(int) is different. I wouldn't have expected that to be an issue, since the size of A2D readings is 14bits or less. I guess another thing to look out for is that right-shift of signed integers is "implementation dependent", and could be different for AVR vs ARM.

It'll be interesting to see what you find.

Ok so more investigation gives the following:
The program is reading acceleration data from an MPU6050 chip which is accessed via I2C. The sensor has three orthogonal axes so the library routine uses vectors and the data from the chip is in two's complement format.

Here are some extracts that I hope will illustrate where I think it is going wrong:
In the header:
struct Vector
{
float XAxis;
float YAxis;
float ZAxis;
};
//Cut many lines of code
private:
Vector ra, rg; // Raw vectors
Vector na, ng; // Normalized vectors
Vector tg, dg; // Threshold and Delta for Gyro
Vector th; // Threshold
Activites a; // Activities

In the .cpp file:

uint8_t xha = Wire.read();
uint8_t xla = Wire.read();
uint8_t yha = Wire.read();
uint8_t yla = Wire.read();
uint8_t zha = Wire.read();
uint8_t zla = Wire.read();
ra.XAxis = xha << 8 | xla;
ra.YAxis = yha << 8 | yla;
ra.ZAxis = zha << 8 | zla;

With the 8bit system all works great and the values of raw acceleration (ra) for the three axes go positive and negative as the sensor is tilted away from horizontal.
With the 32bit system there are no negative values of raw accel and the values jump from very small to 65000+ as it goes through zero.

I suspect it is something to do with the way the float is handled on two's complement numbers between the two systems but I am really struggling now.

Peter

The problem is that with 16 bit int the 8 bit shift would result in a negative number but with 32 it's still positive. The simplest solution would to use a short instead uint8_t to store the values read from the sensor.

Robin

The simplest solution would to use a short instead uint8_t to store the values read from the sensor.

That might not work very well for the low-order byte, but that's definitely the problem. Maybe just the high bytes.
Except: use "int16_t" rather than short, to avoid similar problems in the future!

ra.XAxis = (int16_t)xha << 8 | xla;

Again thanks for the help - sadly no solution yet. Neither int16_t nor short affect the result. On the 32bit system the result is never negative. It seems to be in the use of the float type for the result that is the problem.

Running the code below on either system gives a value for Xaxis of -212 for the 8bit system, and 65324 for the 32bit irrespective of the xha or xla datatype.

int16_t xha=255;
int16_t xla=44;
float Xaxis;
Serial.print(xha);
Serial.print(" ");
Serial.print(xla);
Serial.print(" ");
Xaxis=xha << 8 | xla;
Serial.println(Xaxis);

Peter

Xaxis=xha << 8 | xla;

It's this step where you need to make sure that the result is an int16_t; without care, the calculations will be done with "int" (32bits), which would not do what you want. If you change that to:

    Xaxis=(int16_t)(xha << 8 | xla);

You'll get the correct result.

It would also have worked if you had had:

int16_t xha = (int8_t)255;

@westfw

That did the trick and now the sensor is giving correct results. I modified the sensor library and all seems well.

Once again thank you for your help.

Peter