ADXL330 and Cube 3D, Processing and Arduino

Hi guys,

I’m working on creating a virtual 3D cube which should follow the movements of a physical 3D cube which has an ADXL330 inside of it which is connected to the Arduino board. The program which displays the virtual cube is a Processing sketch.

I think I’m pretty near implementing this but somehow I’m stuck… the virtual cube rotates fine when I just rotate one axis and I comment out the rotations on the other 2 axes, this for all 3 axes.

But, when I’m using rotations on all of the 3 axes it displays a spin when an axis is passing from - to + … when it just shouldn’t.

Note that instead of hardcoding sensitivity, voltages inside the program I preferred to use a calibration process.

Now, this is the Arduino code:

/**
* Reads from a 3 axis accelerometer and send values to serial
*/

// CONSTANTS
#define SAMPLES 64

// INPUTS
#define Y 0
#define Z 1
#define X 2
#define RESET 2


// board inputs variables

int x = 0;
int y = 0;
int z = 0;
int reset = 0;


void setup() {
  analogReference(EXTERNAL);
  
  pinMode(RESET, INPUT);
  digitalWrite(RESET, HIGH); // enable pullup resitor
  
  Serial.begin(9600);  
}


void loop() {
  delay(10); // debounces switches
  
  x = 0;
  y = 0;
  z = 0;
  
  for(int i=0; i<SAMPLES; i++) {
    x += analogRead(X);
    y += analogRead(Y);
    z += analogRead(Z);
    reset = digitalRead(RESET);
  }
  x = x / SAMPLES;
  y = y / SAMPLES;
  z = z / SAMPLES;
  
  Serial.print(x, HEX);
  Serial.print(',');
  Serial.print(y, HEX);
  Serial.print(',');
  Serial.print(z, HEX);
  Serial.print(',');
  Serial.print(reset, HEX);
  Serial.println(',');
  //delay(100);
}

and this is the Processing code:

/**
 * Displays values coming from an accelerometer connected to Arduino
*/

import processing.serial.*;

Serial myPort;  // Create object from Serial class

int [] a = {0, 0, 0}; // accelerations x,y,z
int [] zero = {0, 0, 0}; // values for no acceleration
int [] gmax = {0, 0, 0}; // values when applied full positive g acc
int [] gmin = {0, 0, 0}; // values when applied full negative g acc


float Xrot;
float Yrot;
float Zrot;


int reset = 1;
int oldreset = 1;


final int VIEW_SIZE_X = 600, VIEW_SIZE_Y = 600;

int lf = 10; // 10 is '\n' in ASCII
final int BUFFLEN = 100;
byte[] inBuffer = new byte[BUFFLEN];

boolean calibrated = false;
final int ACCTHRESHOLD = 3;

void setup() 
{
  size(VIEW_SIZE_X, VIEW_SIZE_Y, P3D);
  myPort = new Serial(this, "/dev/ttyUSB0", 9600);
}


void draw() {
  
  if(!calibrated) {
    calibrate();
  }
  
  
  int [] values = readAccelerometer();
  
  for(int i = 0; i<3; i++) {
    /*if(values.length < 4) {
      printArr(values); exit();
    }*/
    int newacc = zero[i] - values[i];
    a[i] = (abs(newacc - a[i]) > ACCTHRESHOLD) ? newacc : a[i];
  }
  
  float g = (0.3 * 3) / 1024; // sensitivity V/g : max volt = g : max digital sesitivity
  //println(g); 
  background(#000000);
  
  
  translate(VIEW_SIZE_X / 2, VIEW_SIZE_Y / 2, -400);
  
  //println(((float) a[0] * 3.0 / 1024.0 - 1.5) / 0.3);
  
  pushMatrix();
    scale(6,6,14);
      
    
    Xrot = -atan2(a[1], a[2]);
    Yrot = atan2(a[0], a[2]); // OK
    Zrot = atan2(a[0], a[1]); // OK
    
    
    rotateX(Xrot); // OK
    rotateY(Zrot); // OK
    rotateZ(Yrot); // OK
    
    
    buildBoxShape();
    
  popMatrix();
  
  fill(#ffffff);
  translate(-VIEW_SIZE_X/2, -VIEW_SIZE_Y/2, 0);
  PFont font;
  // The font must be located in the sketch's 
  // "data" directory to load successfully
  font = loadFont("CourierNew36.vlw"); 
  textFont(font);
  String debug = a[0] + " " + a[1] + " " + a[2] + "\n" + degrees(Xrot) + " " + degrees(Yrot) + " " + degrees(Zrot)/*+ "\n" + xmin + " " + ymin + " " + zminhx + " " + hy + " " + hz */;
  text(debug, 20, 0); 
  
}


void buildBoxShape() {
  //box(60, 10, 40);
  noStroke();
  beginShape(QUADS);
  
  //Z+ (to the drawing area)
  fill(#00ff00);
  vertex(-30, -5, 20);
  vertex(30, -5, 20);
  vertex(30, 5, 20);
  vertex(-30, 5, 20);
  
  //Z-
  fill(#0000ff);
  vertex(-30, -5, -20);
  vertex(30, -5, -20);
  vertex(30, 5, -20);
  vertex(-30, 5, -20);
  
  //X-
  fill(#ff0000);
  vertex(-30, -5, -20);
  vertex(-30, -5, 20);
  vertex(-30, 5, 20);
  vertex(-30, 5, -20);
  
  //X+
  fill(#ffff00);
  vertex(30, -5, -20);
  vertex(30, -5, 20);
  vertex(30, 5, 20);
  vertex(30, 5, -20);
  
  //Y-
  fill(#ff00ff);
  vertex(-30, -5, -20);
  vertex(30, -5, -20);
  vertex(30, -5, 20);
  vertex(-30, -5, 20);
  
  //Y+
  fill(#00ffff);
  vertex(-30, 5, -20);
  vertex(30, 5, -20);
  vertex(30, 5, 20);
  vertex(-30, 5, 20);
  
  endShape();
}


void calibrate() {
  println("Put accelerometer with Z pointing down, then press reset button");
  int [] values = getCalibration();
  
  zero[0] = values[0]; // X doesn't get any acceleration
  zero[1] = values[1]; // Y doesn't get any acceleration
  
  gmax[2] = values[2]; // store g value when Z pointing down
  
  
  println("Put accelerometer with Z pointing up, then press reset button");
  values = getCalibration();  
  gmin[2] = values[2]; // store g value when Z pointing up
  
  println("Put accelerometer with X pointing up, then press reset button");
  values = getCalibration();  
  gmin[0] = values[0]; // store g value when Z pointing up
  zero[2] = values[2]; // Z doesn't get any acceleration
  
  println("Put accelerometer with X pointing down, then press reset button");
  values = getCalibration();  
  gmax[0] = values[0]; // store g value when Z pointing up
  
  
  println("Put accelerometer with Y pointing up, then press reset button");
  values = getCalibration();  
  gmin[1] = values[1]; // store g value when Z pointing up
  
  println("Put accelerometer with Y pointing down, then press reset button");
  values = getCalibration();  
  gmax[1] = values[1]; // store g value when Z pointing up
  
  println("zero: ");
  printArr(zero);
  
  println("gmax: ");
  printArr(gmax);
  
  println("gmin: ");
  printArr(gmin);
  
  calibrated = true;
}


/**
 * Returns values read from accelerometer only when reset button released
*/
int [] getCalibration() {
  int oldreset = 1;
  int [] values = {0, 0, 0, 1};
  
  while(values == null || !(values[3] == 1 && oldreset == 0)) { // cycle until we are not releasing button
    if(values != null) {
      oldreset = values[3];
      //println(values[3] + " " + oldreset);
    }
    values = readAccelerometer();
  }
  return values;
}


/**
 * Get x,y,z and reset values coming from Serial interface. 
*/
int [] readAccelerometer() {
  int [] values = null;
  
  if(myPort.available() > 0 && myPort.readBytesUntil(lf, inBuffer) > 0) {
    values = new int[4];
    
    String inputString = new String(inBuffer);
    String [] inputStringArr = split(inputString, ',');
    
    values[0] = unhex(inputStringArr[0]); // x
    values[1] = unhex(inputStringArr[1]); // y
    values[2] = unhex(inputStringArr[2]); // z
    values[3] = unhex(inputStringArr[3]); // reset
    
    return values;
  }
  else {
    delay(10);
    return readAccelerometer();
  }
}


void printArr(int [] arr) {
  for(int i = 0; i < arr.length; i++) {
    print(arr[i] + " ");
  }
  println("");
}

Thanks for your help,

Fabio Varesano

I did something like this with a memsic2125 2-axis accelerometer. It didn’t seem that complex though. You would have to adapt the code for reading the outputs from the ADXL330 as I’m sure it’s not the same.

Arduino code:

#include <SoftwareServo.h> 
#include <math.h>

SoftwareServo myservo1;  // create servo object to control a servo 
SoftwareServo myservo2;

const int xPin = 2;            // X output of the accelerometer
const int yPin = 3;            // Y output of the accelerometer

void setup() {
  
  Serial.begin(115200);

  myservo1.attach(9);   
  myservo2.attach(10);
  
  pinMode(xPin, INPUT);
  pinMode(yPin, INPUT);
}

void loop() {
  
  int potVal = map(analogRead(0), 0, 1023, -20, 20);  //calibration pots
  int potVal2 = map(analogRead(1), 0, 1023, -20, 20);  
  
  long pulseX, pulseY;
  float accelerationX, accelerationY;
  
  pulseX = pulseIn(xPin,HIGH);  
  pulseY = pulseIn(yPin,HIGH);
  
  accelerationX = ((pulseX / 10) - 500) * 8;
  accelerationY = ((pulseY / 10) - 500) * 8;
  
  int angleX = 179-(180/3.14159)*acos(accelerationX/1000)+potVal;
  int angleY = (180/3.14159)*acos(accelerationY/1000)+potVal2;  
  
  //myservo1.write(angleX);   // sets the servo position according to the scaled value 
  
  //myservo2.write(angleY);
  
  //delay(10);
  
  //SoftwareServo::refresh();
  
  //delay(10);                            

  //SoftwareServo::refresh();
  
  //Serial.print(accelerationX);
  //Serial.print("\t");
  //Serial.print(accelerationY);
  //Serial.print("\t");
  Serial.print(angleX);
  Serial.print("a");
  Serial.print(angleY);
  Serial.print("b");
  
}

There is some code commented out… That was used to control two servos which physically mirrored the orientation of my sensor. The servos were mounted such that a little cardboard box would act like the sensor. Ignore that code.

Processing code:

import processing.serial.*;
import processing.opengl.*;

Serial myPort;
int input;
float X = 0;
float Y = 0;
int input_temp;
int data_last;

void setup() {
  
  size(400, 400, OPENGL);
  stroke(255);
  background(255, 255, 255);
  frameRate(30);
  
  println(Serial.list());
  myPort = new Serial(this, Serial.list()[8], 115200);
  
} 

void draw() {
  background(255,255,255);
  stroke(0);
  strokeWeight(7);
  strokeJoin(MITER);
  translate(200, 200, 200); 
  rotateZ(PI/2-Y);
  //print(xAngle);
  //print("\t");
  rotateX(PI/2-X);
  //println(yAngle);
  fill(255,0,0);
  //sphere(50);
  //sphereDetail(15);
  box(50, 10, 125);
  
}

void serialEvent(Serial p) {
  input = myPort.read();
  //println(input - 48);
  //delay(100);
  switch (input)
  {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      input_temp = input_temp * 10 + input - 48;
      break;
    case 'a':
      X = (input_temp + 15) * PI / 180;
      //print("X:");
      //println(X);
      input_temp = 0;
      break;
    case 'b':
      Y = (input_temp + 10) * PI / 180;
      //print("Y:");
      //println(Y);
      input_temp = 0;
      point(X, Y);
      break;
    case 'C':
      println(input_temp);
      if (input_temp < 205)
      {
        stroke(input_temp, 0, 0);
      }
      else if (input_temp > 255 && input_temp < 512)
      {
        stroke(0, input_temp-256, 0); 
      }
      else if (input_temp > 512)
      {
        stroke(0, 0, input_temp-512); 
      }
      input_temp = 0;
      break;
    case 'T':
      strokeWeight(input_temp);
      input_temp = 0;
      break;
  }
}

This code takes serial input from the arduino and draws a box. The angle on the x and y axes are calculated from the accelerometer data. Hope that helps you.

Thanks, would you please explain the following code?

int angleX = 179-(180/3.14159)*acos(accelerationX/1000)+potVal;
  int angleY = (180/3.14159)*acos(accelerationY/1000)+potVal2;

That takes the accelerations measured from the sensor(in milli g's) and converts them into the necessary angles for setting the the angle of the block displayed in processing. acceleration/1000 sets the range from -1 to 1. As far as what its for is kind of irrelevant to your application. I'm sure that your sensor is likely analog(easier to read). You need to take your readings from the two horizontal axes, and calculate the angle(about x & y) with those. The additional z axis is used for telling if its positive or negative. I would suggest that you map your sensor reading such that 1g = 1,000. This makes the arithmetic easier.