Tilt underwater

First post. Here goes.

For work I measure currents and waves in the sea. Sometimes I need to place instruments on the seabed to do this. In these circumstances the instruments are mounted on a frame which is lowered to the seabed from a vessel where they stay for typically 2 or 3 months while they do their thing autonomously. Some of these instruments need to sit vertical (plus or minus 10 or 15 degrees) to work properly so it is crucial that I know that they are nicely placed on the seabed before I leave them. I don’t want pitch and roll, I want a single value of tilt from 0 to 180 degrees.

I am trying to build a system that measures the tilt and transmits this information acoustically, through the water column, to me at the surface. The frames are lowered to the seabed on a rope. If I know that the frame is tilted (perhaps it landed on a rock), I can lift the frame and put it down somewhere else before releasing the frame and recovering the rope.

My gadget will be rigidly fixed to the seabed frame alongside the other instruments. It uses an ADXL345 accelerometer and an Arduino Nano to provide a single value of tilt. These items will be in an underwater housing. The Arduino will send the single tilt value to another system which will transmit the information acoustically through the sea water to me at the surface via a hydrophone and receiving system.

The through water communications are sorted but deriving a single value of tilt from the Arduino and accelerometer is proving difficult.

My Nano and ADXL345 are working fine. I have calibrated the sensor and applied offset and gain factors. I can get 3 realistic values in G’s and I can get pitch and roll plus and minus 180 degrees but I don’t know how to derive the single tilt value that I need.

On this forum I have seen references to links such as this one.

http://cache.freescale.com/files/sensors/doc/app_note/AN4248.pdf

But to be honest the maths is way beyond me. I suppose I am asking if anyone can help me out with some code or at least point me in the right direction.

Here is the code I use to get my G values, pitch and roll.

/*  Uses I2C comms with 2 x 4.7K resistors 
 *  Uses sparkfun ADXL345 library
 *  This one applys Offset and gain values derived by calibration
 *  Outputs G values which are then used derive pitch and roll values + and - 180 d.
 *  Offset and Gain values are when set to +or- 2g mode.

*/
#include <SparkFun_ADXL345.h>     


//*********** COMMUNICATION SELECTION ***********/
/*    Comment Out The One You Are Not Using    */
//ADXL345 adxl = ADXL345(10);           // USE FOR SPI COMMUNICATION, ADXL345(CS_PIN);
ADXL345 adxl = ADXL345();             // USE FOR I2C COMMUNICATION


//****************** VARIABLES ******************/
/*                                             */
float accX;
float accY;
float accZ;
float pitch,roll;


//************** DEFINED VARIABLES **************/
/*                                             */
#define offsetX   14       // OFFSET values
#define offsetY   2
#define offsetZ   -11

#define gainX     260        // GAIN factors
#define gainY     264
#define gainZ     253 


//******************** SETUP ********************/
/*          Configure ADXL345 Settings         */
void setup(){
  
  Serial.begin(9600);                 // Start the serial terminal
  
  adxl.powerOn();                     // Power on the ADXL345

  adxl.setRangeSetting(2);           // Give the range settings
                                      // Accepted values are 2g, 4g, 8g or 16g
                                      // Higher Values = Wider Measurement Range
                                      // Lower Values = Greater Sensitivity

  //adxl.setSpiBit(0);                  // Configure the device to be in 4 wire SPI mode when set to '0' or 3 wire SPI mode when set to 1
                                      // Default: Set to 1
                                      // SPI pins on the ATMega328: 11, 12 and 13 as reference in SPI Library 
                                      // Preseume not needed in I2C mode
}



//****************** MAIN CODE ******************/
/*     Accelerometer Readings and Interrupt    */
void loop(){


    // Get the Accelerometer Readings

    int x,y,z; 
       
    adxl.readAccel(&x, &y, &z);         // Read the accelerometer values and store them in variables declared above x,y,z
           
    Serial.print("x y z: "); Serial.print(x); Serial.print("  ");Serial.print(y); Serial.print("  "); Serial.print(z);

  
    //Apply offsets
  accX = (x - offsetX);         // Calculating New Values for X, Y and Z with offsets applied
  accY = (y - offsetY);
  accZ = (z - offsetZ);
  
    //Apply gains
  accX = (accX / gainX);         // Calculating G Values for X, Y and Z  with offsets and gains applied
  accY = (accY / gainY);
  accZ = (accZ / gainZ);

  Serial.print("  Converted to G values: "); Serial.print(accX); Serial.print("  "); Serial.print(accY); Serial.print("  "); Serial.print(accZ);
  Serial.println();


    roll  = (atan2(-accY, accZ)*180.0)/M_PI;
    pitch = (atan2(accX, accZ)*180.0)/M_PI;

    Serial.print("  Pitch =");
    Serial.print(pitch);  

    Serial.print("  Roll =");
    Serial.println(roll);


    
  delay(500);                          // This is the delay between each reading

 
    }

Thanks in anticipation.

If there is tilt on two axes simultaneously, then one of these two equations is not correct.

    roll  = (atan2(-accY, accZ)*180.0)/M_PI;
    pitch = (atan2(accX, accZ)*180.0)/M_PI;

The result depends on the order that the rotations are applied, and there are unfortunately six different conventions, additionally combined with ambiguity of the sign of the rotation.

I prefer the pitch and roll definitions in this tutorial, which uses the "Rxyz" convention described therein.

 roll = atan2(y_Buff , z_Buff) * 57.3;
  pitch = atan2((- x_Buff) , sqrt(y_Buff * y_Buff + z_Buff * z_Buff)) * 57.3;

The two tilt angles can be converted into a single tilt, with an axis that has some determined direction with respect to X,Y,Z axes of the accelerometer. However, the conversion again depends on exactly how you define pitch and roll, and the order in which you perform the operations.

When you have sorted out which definition you will be using, come back. Some trigonometry and algebra will be involved.

Edit: There is another approach to calculate a single tilt angle, described in this much better and more complete reference to tilt calculations. See section 4 "Calculating the Angle Between Two Accelerometer Readings".

To use this method, you will first have to obtain the three accelerometer axial measurements when the device is perfectly upright, which could be done on land. Then it is a simple matter to use the three axial measurements obtained when the device is resting on the sea bottom, to calculate the tilt angle of interest.

Have you checked out the Cave Pearl Project? https://thecavepearlproject.org/

From your description of what you want it seems like you don't care which way the frame is tilted as long as it is not tilted too much in any direction, is that correct?

If so, then can I make the very simplistic suggestion that you just report the maximum absolute reading?
E.g. roll is 5 degrees, pitch is -15 degrees, so you report 15 degrees.

Or, do some math to calculate the degree of tilt based on both pitch and roll.
Time to break out those trig skills!

I crunched it out. As always, my math is suspect. But this is what I got.

If your pitch is A and Roll is B, then C, your combined tilt is defined as:

C = ACOS( COS(A) * COS(B) )

That simple expression should work, but only in the case that the accelerometer has the Z axis perfectly vertical, when the instrument is perfectly vertical, which requires careful alignment during construction.

The simplest approach to the general case, which does not require any special alignment of the accelerometer (but does require two readings), is given in Section 4 of this guide.

Thanks jremington, it is now nearly working. Still something wrong at the end though.

The cos value of tilt looks realistic but the conversion to degrees is wrong. This is surely something due to my beginner status with code.

Here is my latest code.

/*  Uses I2C comms with 2 x 4.7K resistors 
 *  Uses sparkfun ADXL345 library
 *  This one applys Offset and gain values derived by calibration
 *  Outputs G values which are then used with freescale equation 50 to get tilt
 *  Offset and Gain values are when set to +or- 2g mode.
 *  G values mapped for vertical use

*/
#include <SparkFun_ADXL345.h>     


//*********** COMMUNICATION SELECTION ***********/
/*    Comment Out The One You Are Not Using    */
//ADXL345 adxl = ADXL345(10);           // USE FOR SPI COMMUNICATION, ADXL345(CS_PIN);
ADXL345 adxl = ADXL345();             // USE FOR I2C COMMUNICATION


//****************** VARIABLES ******************/
/*                                             */
float Xo;
float Yo;
float Zo;

float Xos;
float Yos;
float Zos;

float Xmap;
float Ymap;
float Zmap;

float Modmove;

float Costilt;
float Tilt;


//************** DEFINED VARIABLES **************/
/*                                             */
#define offsetX   14       // OFFSET values
#define offsetY   2
#define offsetZ   -11

#define gainX     260        // GAIN factors
#define gainY     264
#define gainZ     253 

#define Xvert     0.001
#define Yvert     0.001
#define Zvert     1.001
#define Modvert   1.001

//******************** SETUP ********************/
/*          Configure ADXL345 Settings         */
void setup(){
  
  Serial.begin(9600);                 // Start the serial terminal
  
  adxl.powerOn();                     // Power on the ADXL345

  adxl.setRangeSetting(2);           // Give the range settings
                                      // Accepted values are 2g, 4g, 8g or 16g
                                      // Higher Values = Wider Measurement Range
                                      // Lower Values = Greater Sensitivity

  //adxl.setSpiBit(0);                  // Configure the device to be in 4 wire SPI mode when set to '0' or 3 wire SPI mode when set to 1
                                      // Default: Set to 1
                                      // SPI pins on the ATMega328: 11, 12 and 13 as reference in SPI Library 
                                      // Preseume not needed in I2C mode
}



//****************** MAIN CODE ******************/
/*     Accelerometer Readings and Interrupt    */
void loop(){


    // Get the Accelerometer Readings

    int x,y,z; 
       
    adxl.readAccel(&x, &y, &z);         // Read the accelerometer values and store them in variables declared above x,y,z
           
    Serial.print("x y z: "); Serial.print(x); Serial.print("  ");Serial.print(y); Serial.print("  "); Serial.print(z);

  
    //Apply offsets
  Xo = (x - offsetX);         // Calculating New Values for X, Y and Z with offsets applied
  Yo = (y - offsetY);
  Zo = (z - offsetZ);
  
    //Apply gains
  Xos = (Xo / gainX);         // Calculating G Values for X, Y and Z  with offsets and gains applied
  Yos = (Yo / gainY);
  Zos = (Zo / gainZ);

  Serial.print("  Converted to G values: "); Serial.print(Xos); Serial.print("  "); Serial.print(Yos); Serial.print("  "); Serial.print(Zos);
  

  Modmove = sqrt((Xos*Xos)+(Yos*Yos)+(Zos*Zos));

  Serial.print("  Modvert  : "); Serial.print(Modvert); 
  Serial.print("  Modmove  : "); Serial.print(Modmove); 
 
  Costilt = (Xvert*Xos)+(Yvert*Yos)+(Zvert*Zos)/(Modvert*Modmove);
  
  Serial.print("  Costilt  : "); Serial.print(Costilt); 


  Tilt = acos(Costilt);

  Serial.print("  Tilt: "); Serial.print(Tilt); 
  Serial.println();

    
  delay(500);                          // This is the delay between each reading

 
    }

The accelerometer is at a tilt of about 11 degrees and the output looks like this

y z: -31 -15 239 Converted to G values: -0.17 -0.06 0.99 Modvert : 1.00 Modmove : 1.01 Costilt : 0.98 Tilt: 0.19
x y z: -32 -15 240 Converted to G values: -0.18 -0.06 0.99 Modvert : 1.00 Modmove : 1.01 Costilt : 0.98 Tilt: 0.19
x y z: -32 -15 239 Converted to G values: -0.18 -0.06 0.99 Modvert : 1.00 Modmove : 1.01 Costilt : 0.98 Tilt: 0.19

Thanks for any help.

Leroy
I suspect the angles on this one only go 0 to 90.

bms001
That wont work. If pitch was 44 and roll was 45 the reported angle would be 45 wheras the real value would be considerably greater

Bnorm:
bms001
That wont work. If pitch was 44 and roll was 45 the reported angle would be 45 wheras the real value would be considerably greater

45 pitch and 45 roll equates to 60 combined tilt. This one stands out because it is an all-integer result.

This line:

  Costilt = (Xvert*Xos)+(Yvert*Yos)+(Zvert*Zos)/(Modvert*Modmove);

needs to be changed to

Costilt = (Xvert*Xos + Yvert*Yos + Zvert*Zos)/(Modvert*Modmove);

(The first line, with the incorrect formulation, "sort of" worked because Xvert and Yvert are so small).

To get degrees from acos(), use

Tilt = (180.0/M_PI)*acos(Costilt);

Did you calibrate that particular accelerometer to arrive at the offsets and scale factors used in the program?

If not, that needs to be done for each accelerometer individually. See this tutorial: Tutorial: How to calibrate a compass (and accelerometer) with Arduino | Underwater Arduino Data Loggers

That works a treat. Thank you.