ADXL335 as a 2 axis level using variable starting points

I'm wanting to use a ADXL335 analog accelerometer as a simple 2 axis level to detect movement. I do not care what angle it is at when moved (other than +- range padding). I only care that it HAS moved via in this example with an LED. The cleaned up code below does what I want other than (and this is the issue)... it depends on me placing the ADXL335 at the same exact starting level each and every time. This will be placed in new and varying locations so I think I need a way to sample the current pins, average them, pad range values, set them as Mins Maxes, then compare to the current pins.

This is my very first post. I have looked around but not everything meshes with my device or has range padding.
Even helping me with proper keywords and links would be appreciated.

// Adafruit ADXL335 (5v w/AREF) + Uno R3 (5v) + 1 LED
const int xPin = A0;               // x-axis of the ADXL335
const int yPin = A1;               // y-axis of the ADXL335
const int ledPin = 13;             // LED w/resistor
long xy_min = 505;                 // From me watching the Serial Monitor then - Padding
long xy_max = 520;                 // From me watching the Serial Monitor then + Padding

long x = 0;                        // clear values to be set below I guess
long y = 0;                        // clear values to be set below I guess

void setup() {
  pinMode(ledPin, OUTPUT);        // Set LED pin 13 as output
}

void loop() {
  analogReference(EXTERNAL);      // ADXL335's 3.3v pin connected to Uno's AREF pin
  x = analogRead(xPin);           // Current X value
  y = analogRead(yPin);           // Current y value

  if (y <= xy_max && y >= xy_min && x >= xy_min && x <= xy_max) {   // compare 
    digitalWrite(ledPin, HIGH);   // Turn LED on
  } else {
    digitalWrite(ledPin, LOW);    // Turn LED off
  }
}

In setup read the current x & y values (maybe a few times to average them) and use them as the base values to calculate xy_min, xy_max from in loop. You will need to have separate max values for x and y and not combined like your currently using.
That way you place the device down and reset it. this then uses its current position as the base values.

The acceleration is a 3D vector, and when the sensor is still, it measures the direction of the acceleration due to gravity, which incidentally represents the sensor tilt (relative to vertical).

It is straightforward to calculate the change in tilt angle from two measurements, that is the angle between the two corresponding acceleration vectors. Check that angle from time to time to see if it exceeds some setting.

I would do something like this (untested). This method cannot detect rotation about the vertical axis.

// Read acceleration vector at program startup
 float xs = analogRead(xPin);           // Starting X value
 float ys = analogRead(yPin);           // Starting Y value
 float zs = analogRead(zPin);         // Starting z value
 float start_length = sqrt(xs*xs + ys*ys + zs*zs);  //length of vector

// later, read new acceleration vector and calculate angle to previous vector in degrees
float  xnew = analogRead(xPin);           // Current X value
float  ynew = analogRead(yPin);           // Current Y value 
float znew = analogRead(zPin);            // Current Z value
float new_length = sqrt(xnew*xnew + ynew*ynew + znew*znew);

// vector dot product of start and new
float dot = xs*xnew + ys*ynew + zs*znew;

// angle between the two vectors in degrees (0 to 180)
float angle = (180.0/PI)*acos(dot/(start_length*new_length));

if (angle > 10.0)  do_something();  //more than 10 degree change in tilt?

jremington, Thank you.
Using your example, this seems to be measuring acceleration instead of angle.
If I'm careful, I'm able to move it all the way to one side or the other without triggering it.

I should had stated clearer that I need to trigger by an angle (vs acceleration)
because the cattle could cause a seismic effect if running near by.
I need to know if it gets knocked over, around, or out of place by angle.

// Adafruit ADXL335 (5v w/AREF) + Uno R3 (5v) + 1 LED
int xPin = A0;               // x-axis of the ADXL335
int yPin = A1;               // y-axis of the ADXL335
int zPin = A2;               // y-axis of the ADXL335
int ledPin = 13;             // LED w/resistor

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);         // Set LED pin 13 as output
}

void loop() {
  analogReference(EXTERNAL);       // ADXL335's 3.3v pin connected to Uno's AREF pin

  // Read acceleration vector at program startup
  float xs = analogRead(xPin);     // Starting X value
  float ys = analogRead(yPin);     // Starting Y value
  float zs = analogRead(zPin);     // Starting Z value
  float start_length = sqrt(xs * xs + ys * ys + zs * zs); //length of vector

  Serial.print(" Start = ");
  Serial.print(start_length);
  Serial.print("  - ");

  // later, read new acceleration vector and calculate angle to previous vector in degrees
  float xnew = analogRead(xPin);   // Current X value
  float ynew = analogRead(yPin);   // Current Y value
  float znew = analogRead(zPin);   // Current Z value
  float new_length = sqrt(xnew * xnew + ynew * ynew + znew * znew);

  Serial.print(" New = ");
  Serial.print(new_length);
  Serial.print(" - ");

  // vector dot product of start and new
  float dot = xs * xnew + ys * ynew + zs * znew;

  Serial.print(" Dot = ");
  Serial.print(dot);
  Serial.print(" - ");

  // angle between the two vectors in degrees (0 to 180)
  float angle = (180.0 / PI) * acos(dot / (start_length * new_length));

  Serial.print(" Angle = ");
  Serial.print(angle);

  if (angle > 0.20)  {  //more than 10 degree change in tilt? (Note, changed to 0.10 vs 10)
    digitalWrite(ledPin, HIGH);   // Turn LED on

  Serial.print(" - ");
  Serial.print(" LED Triggered ");
  delay(1000);

  } else {
    digitalWrite(ledPin, LOW);    // Turn LED off
  }
  Serial.print("\r\n");
}
  // Read acceleration vector at program startup
  float xs = analogRead(xPin);     // Starting X value
...

sets the reference orientation and is done ONCE, at startup. It should be in setup(), as the comment suggests. . The comments are important!

xs, ys, zs, start_length need to be global variables (declared outside of loop() or setup()).

As follows:

float xs, ys, zs, start_length;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);         // Set LED pin 13 as output

  xs = analogRead(xPin);     // Starting X value
  ys = analogRead(yPin);     // Starting Y value
  zs = analogRead(zPin);     // Starting Z value
  start_length = sqrt(xs * xs + ys * ys + zs * zs); //length of vector

}

This seems to work except my device is not starting with an angle of zero.
It depends on me placing the ADXL335 at the same exact starting level each and every time. This will be placed in new and varying locations so I need it compare to whatever starting xyz axis is at vs it being "actually level". I have calibrated using the button found on their page.... Adafruit Calibration Sketch but as you can see below, the monitor shows I'm starting at an angle of 2.32. Tilting left takes the value higher while tilting right takes it lower toward zero then takes it higher. Resetting did not change the outcome.

Is there a way to zero it to the current axis?

-Sitting Flat--------------------------
  X        Y       Z    Start    New
391.00, 357.00, 430.00, 682.08, 
515.00, 515.00, 515.00,       , 953.91
 Dot = 650108.00 - Angle = 2.32
--------------------------------------
-Tilting Left-------------------------
  X        Y       Z    Start    New
391.00, 357.00, 430.00, 682.08, 
449.00, 449.00, 449.00,       , 905.72
 Dot = 615126.00 - Angle = 5.30 -  LED Triggered 
--------------------------------------
-Tilting Right------------------------
  X        Y       Z    Start    New
391.00, 357.00, 430.00, 682.08, 
573.00, 573.00, 573.00,       , 975.20
 Dot = 665046.00 - Angle = 1.08
--------------------------------------
-Sitting Flat--------------------------
  X        Y       Z    Start    New
391.00, 357.00, 430.00, 682.08, 
515.00, 515.00, 515.00,       , 953.91
 Dot = 650108.00 - Angle = 2.32
--------------------------------------

Current Code

// Adafruit ADXL335 (5v w/AREF) + Uno R3 (5v) + 1 LED
int xPin = A0;                // x-axis of the ADXL335
int yPin = A1;                // y-axis of the ADXL335
int zPin = A2;                // y-axis of the ADXL335
int ledPin = 13;              // LED w/resistor

float xs, ys, zs, start_length;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);    // Set LED pin 13 as output

  xs = analogRead(xPin);      // Starting X value
  ys = analogRead(yPin);      // Starting Y value
  zs = analogRead(zPin);      // Starting Z value
  start_length = sqrt(xs * xs + ys * ys + zs * zs); //length of vector
}

void loop() {
  analogReference(EXTERNAL); // ADXL335's 3.3v pin connected to Uno's AREF pin

  // later, read new acceleration vector and calculate angle to previous vector in degrees
  float xnew = analogRead(xPin);   // Current X value
  float ynew = analogRead(yPin);   // Current Y value
  float znew = analogRead(zPin);   // Current Z value
  float new_length = sqrt(xnew * xnew + ynew * ynew + znew * znew);


  // vector dot product of start and new
  float dot = xs * xnew + ys * ynew + zs * znew;

  // angle between the two vectors in degrees (0 to 180)
  float angle = (180.0 / PI) * acos(dot / (start_length * new_length));

  // Creating monitor logs just to see whats happening
  Serial.print("--------------------------------------");
  Serial.print("\r\n");
  Serial.print("  X        Y       Z    Start    New");
  Serial.print("\r\n");
  Serial.print(xs);
  Serial.print(", ");
  Serial.print(ys);
  Serial.print(", ");
  Serial.print(zs);
  Serial.print(", ");
  Serial.print(start_length);
  Serial.print(", ");
  Serial.print("\r\n");
  Serial.print(xnew);
  Serial.print(", ");
  Serial.print(xnew);
  Serial.print(", ");
  Serial.print(xnew);
  Serial.print(",       , ");
  Serial.print(new_length);
  Serial.print("\r\n");
  Serial.print(" Dot = ");
  Serial.print(dot);
  Serial.print(" - ");
  Serial.print("Angle = ");
  Serial.print(angle);

  if (angle > 3)  {  //more than 10 degree change in tilt? (Note, changed to 0.10 vs 10)
    digitalWrite(ledPin, HIGH);   // Turn LED on

  Serial.print(" - ");
  Serial.print(" LED Triggered ");

  } else {
    digitalWrite(ledPin, LOW);    // Turn LED off
  }
  Serial.print("\r\n");
  delay(4000); // just so I have time to tilt it around for the next reading
}

The code currently measures the difference in tilt angle between the starting orientation and some later orientation.

Determining absolute inclination or true level is a completely different process, but you can do both at the same time.

I do not care what angle it is at when moved (other than +- range padding). I only care that it HAS moved

At some point, you will need to decide what you really want to do.

Note: This call:

  analogReference(EXTERNAL); // ADXL335's 3.3v pin connected to Uno's AREF pin

MUST be done BEFORE you call analogRead().

It should NOT be done in loop(). You may have already damaged your Arduino.

At some point, you will need to decide what you really want to do.

What I decided I want it to do is....
Take real world factors into consideration. The Great Outdoors are rarely level.
Three months ago the device could had been sitting in a fab with a perfect lab environment.
Today the device is sitting on an uneven rock near a creek.
Tomorrow the device will be sitting on an uneven tree stump in a field.
Cows may run past the device causing very small seismic effects.

A current pin reading variable set at near startup is used as the NEW (insert correct term for center, zero, or level). If the device is moved very slowly over time by the wind or a curious racoon decides it needs to be washed before eaten, I need to (in this example) turn on an LED if the device moves past a settable +- threshold set by whatever method is used compared to the variable that was set above. It would be nice if acceleration (seismic activity) registered within the threshold above would be ignored, but realize this might exceed the nature of the device design.

"The overall" degree or angle the device ends up with resulting from it laying upside down in a creek or laying on its side on the tree stump is the "whatever I didn't care about" at the beginning of this thread. I only care that it has exceeded the conditions above for which an action is trigger.

In the last code I posted, it seemed to have my original problem of using a static location as a starting point rather using its current location as a starting point. I have moved the analogReference(EXTERNAL); to the correct place but the serial monitor still shows an Angle other than zero when stationary.

If I understand your last post correctly, the code does what you want, but you don't yet know how to use it properly.

When the Arduino is reset, it reads and sets the current sensor orientation as the reference orientation.

To set the reference orientation at any later time, either cycle the power on the Arduino, or reset the Arduino using the reset button provided on some Arduino models, or add a pushbutton to call a function that simply repeats the last four lines in setup().

Hint: it is a good idea to make comments that are correct and meaningful:

  if (angle > 3)  {  //more than 10 degree change in tilt? (Note, changed to 0.10 vs 10)

The above should read "more than 3 degree change in tilt?"

The reset button was tried before, pushing the sketch again was tried before.
Cycling power resolved the issue.
jremington
Thank you for your patience.

When reading new acceleration vectors and calculate angle to previous vector in degrees....
What would be the best way to take a set amount of samples from xPin & yPin and return them as an average (vs raw) to smooth it out a little and reduce small seismic effects.

  // Read new acceleration vector and calculate angle to previous vector in degrees
  float xnew = analogRead(xPin);   // Current X value
  float ynew = analogRead(yPin);   // Current Y value
  float new_length = sqrt(xnew * xnew + ynew * ynew);

Note; There will be other devices connected to this Uno.

One could read each pin several times and calculate an average.

Example

unsigned int xave = 0;
for (byte i=0; i<10; i++) {
   xave += analogRead(xpin);
   delay(20); //delay not required, use only as needed
   }
xave /= 10;  //average of ten readings

jremington
That did it.
I've seen that being used but the variables were throwing me off until you placed them in prospective.
Thank you.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.