Author Topic: Assistance with reading an accelerometer with I2C/wire.h  (Read 9420 times)

jwhendy

  • Newbie
  • *
  • Posts: 44
Assistance with reading an accelerometer with I2C/wire.h
« on: March 19, 2016, 10:45:57 am »
Hello,


I was trying to port some code I've used before on an arduino to the oak. Here's the code:

Code: [Select]
#include <Wire.h>

// wasn't sure if this was necessary; I've tried both ways
// #include <SPI.h>

// adxl device for wire.h
#define DEVICE (0x53)

// adxl setup
byte _buff[6];
char POWER_CTL = 0x2D;  //Power Control Register
char DATA_FORMAT = 0x31;
char DATAX0 = 0x32; //X-Axis Data 0
char DATAX1 = 0x33; //X-Axis Data 1
char DATAY0 = 0x34; //Y-Axis Data 0
char DATAY1 = 0x35; //Y-Axis Data 1
char DATAZ0 = 0x36; //Z-Axis Data 0
char DATAZ1 = 0x37; //Z-Axis Data 1
uint8_t howManyBytesToRead = 6;

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

long now = millis();

void setup() {

  // I comment all of these out when debugging arduino
  Particle.variable("x_val", x);
  Particle.variable("y_val", y);
  Particle.variable("z_val", z);

  // join i2c bus (address optional for master)
  Wire.begin();

  // Put the ADXL345 into +/- 4G range by writing the value 0x01 to the DATA_FORMAT register.
  // think 00x0 = 2g, 00x1 = 4g, 00x2 = 8g, and 00x3 = 16g
  writeTo(DATA_FORMAT, 0x01);

  //Put the ADXL345 into Measurement Mode by writing 0x08 to the POWER_CTL register.
  writeTo(POWER_CTL, 0x08);

  Serial.begin(9600);

}


// the loop routine runs over and over again forever:
void loop() {

  if(millis() - now > 500)
  {

    //read the acceleration data from the ADXL345
    readFrom(DATAX0, howManyBytesToRead, _buff);
    x = (((int)_buff[1]) << 8) | _buff[0];
    y = (((int)_buff[3]) << 8) | _buff[2];
    z = (((int)_buff[5]) << 8) | _buff[4];

    // for debug when using with arduino
    Serial.println(x);

    now = millis();

  }

}

///////// begin accelerometer reader //////////

void writeTo(byte address, byte val) {
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address);             // send register address
  Wire.write(val);                 // send value to write
  Wire.endTransmission();         // end transmission
}

// Reads num bytes starting from address register on device in to _buff array
void readFrom(byte address, int num, byte _buff[]) {
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address);             // sends address to read from
  Wire.endTransmission();         // end transmission

  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from device

  int i = 0;
  while (Wire.available())        // device may send less than requested (abnormal)
  {
    _buff[i] = Wire.read();    // receive a byte
    i++;
  }

  Wire.endTransmission();         // end transmission
}

////////// end accelerometer reader //////////

I'm getting what I guess I'd call overflow behavior on the oak, but not the arduino. As I run GET commands on the Particle.variable()'s, things are fairly sane until I tilt the accelerometer significantly, at which point my values jump from the range of +/- 30 to 65,xxx. I guess that's why I'm calling it "overflow" but not sure the name for it. Just seems like something is trying to store a value too big and defaults to something really high?

I've wondered if it's voltage related and have tried power the oak with both the 3.3V and 5V from my Arduino, as well as connecting the adxl345 to both the Vcc and Vin lines on the oak. I get sane results with either 3.3 or 5V when troubleshooting on an arduino.

Example output when rotating on Arduino, adxl connected to 3.3V:
Code: [Select]
-13
-12
-13
1
-43
-101
-151
-161
-165
-134
-110
-149
-140
-120
-95
-108
-104
-42
-17
47
81
115
120
101
70
30
74
96
118
95
50
107
94
24
6
-36
-15
-12


Example on oak, powered by 3.3V arduino and adxl connected to Vcc (just grabbed the results line for y_val of the json return):
Code: [Select]
  "name": "y_val",
  "result": 199,
  "result": 247,
  "result": 187,
  "result": 65527,
  "result": 65485,

There's some settings in the data sheet (p17), but I honestly have no idea how to set them properly. There's a few in there that sound like promising potentials, and make me wonder if I need to adjust something:

Quote
The DATA_FORMAT
register controls the presentation of data
to Register 0x32 through Register 0x37. All data, except that for
the ±16 g range, must be clipped to avoid rollover.

FULL_RES Bit
When this bit is set to a value of 1, the device is in full resolution
mode, where the output resolution increases with the g range
set by the range bits to maintain a 4 mg/LSB scale factor. When
the FULL_RES bit is set to 0, the device is in 10-bit mode, and
the range bits determine the maximum g range and scale factor.

Justify Bit
A setting of 1 in the justify bit selects left (MSB) justified mode,
and a setting of 0 selects right justified mode with sign extension.

Any suggestions on what I could check? Perhaps a starter question: should wire.h on an arduino behave just like the oak? Or is there an obvious voltage issue I'm going to run into at 3.3v logic?


PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #1 on: March 19, 2016, 09:00:16 pm »
Hey jwhendry,

I'll dig around in my box of bits later as I think I have a couple of them knocking around (the ADXL345, that is). (EDIT: No, actually I don't! It looks like I the nearest I have (form-factor wise) is the MPU-6050... might still give it a go anyway as it is I2C as well) In the mean time, maybe something in the code this chap used to get it working on the ESP8266 Arduino Core will help. Particuarly how he sets it up in setup(), and how he reads it using readAccel(). A lot of the other code is related to the ESP8266 way of handling wifi, but there might be something there in it that helps?

http://myesp8266.blogspot.com.au/2015/12/various-accelorometers-adxl345-with.html
« Last Edit: March 19, 2016, 09:17:40 pm by pfeerick »

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #2 on: March 19, 2016, 11:01:31 pm »
For reference, using the MPU-6050 that I had from another board (beta backer sensor board for the DigiX??)... shoving it into a breadboard ,and wiring it to first the Arduino (3v3,GND,SDA and SCL only), and it worked just fine on the Arduino. Moved the MPU-6050 and code over to the Oak, and nothing was happening when I was using Particle variables. Used publish messages instead and it started talking sense. (Using Oaks VCC & Ground, SCL 2 and SDA 0). I had my USB to Serial adapter handy too, so I looked at the Serial messages the Oak was sending, and it was happily posting the same output the Arduino had been.

So for that accelerometer at least, I2C/Wire works without any changes. I don't know what the go with the Particle variables was... they just didn't appear to be registering. They did one time, not not since. When I check with curl, I get this:

Code: [Select]
{
  "id": "d957040018c7df4293f7837d",
  "name": "Oak3",
  "last_app": null,
  "last_ip_address": "58.96.45.108",
  "last_heard": "2016-03-20T05:41:23.397Z",
  "product_id": 82,
  "connected": true,
  "platform_id": 82,
  "cellular": false,
  "status": "normal",
  "variables": {},
  "functions": []
}

Anyway, some code is attached below which works. As I don't know a better way yet to detect the Oak core, I cheated and used #ifdefs that looks for the header file. So this code should be Oak & Arduino specific. It delays for 5 seconds between updates to honour the "1 event/sec" rate stated in the Particle docs (although it could have been reduced to 3 seconds!)

Code: [Select]
// Orginal code was :  MPU-6050 Short Example Sketch
// By Arduino User JohnChi
// August 17, 2014
// Public Domain
#include<Wire.h>
const int MPU_addr = 0x68; // I2C address of the MPU-6050
int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;

#ifdef Oak_h
char szAcX[8] = "0";
char szAcY[8] = "0";
char szAcZ[8] = "0";

char szInfo[16];
#endif

void setup()
{
  Wire.begin();
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

  Serial.begin(9600);

#ifdef Oak_h
  Particle.variable("x_val", szAcX);
  Particle.variable("y_val", szAcY);
  Particle.variable("z_val", szAcZ);
  delay(5000); //allow time for particle to register variables (or not!)
#endif
}

void loop()
{
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr, 14, true); // request a total of 14 registers

  AcX = Wire.read() << 8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY = Wire.read() << 8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read() << 8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp = Wire.read() << 8 | Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX = Wire.read() << 8 | Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY = Wire.read() << 8 | Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ = Wire.read() << 8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

  Serial.print("AcX = "); Serial.print(AcX);
  Serial.print(" | AcY = "); Serial.print(AcY);
  Serial.print(" | AcZ = "); Serial.print(AcZ);
  Serial.print(" | Tmp = "); Serial.print(Tmp / 340.00 + 36.53); //equation for temperature in degrees C from datasheet
  Serial.print(" | GyX = "); Serial.print(GyX);
  Serial.print(" | GyY = "); Serial.print(GyY);
  Serial.print(" | GyZ = "); Serial.println(GyZ);

#ifdef Oak_h
  sprintf(szInfo, "%i", AcX);
  Particle.publish("AcX", szInfo);
  memcpy( szAcX, szInfo, 8 );

  sprintf(szInfo, "%i", AcY);
  Particle.publish("AcY", szInfo);
  memcpy( szAcY, szInfo, 8 );

  sprintf(szInfo, "%i", AcZ);
  Particle.publish("AcZ", szInfo);
  memcpy( szAcZ, szInfo, 8 );

  delay(5000);
#else
  delay(500);
#endif
}

jwhendy

  • Newbie
  • *
  • Posts: 44
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #3 on: March 21, 2016, 04:35:48 pm »
@pfeerick: just a quick note so you don't think I missed this! Really grateful for your time/effort! I'll be taking a look in the next couple of days. I've been filling my time writing tutorials for the moment :)

I'm near positive I read the serial output through an arduino in addition to Particle.variable() and saw the same behavior. It reminded me of some sort of overflow... just too coincidental that the values would jump from sane to 65,xxx? Or is that just a coincidence??

I'll give a whirl to Particle.publish(), though I did get values back using Particle.variable().

Anyway, I'll take another look for sure, and at least you've confirmed that nothing should be Oak-specific re. wire.h. Thanks again!

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #4 on: March 22, 2016, 03:58:27 am »
No problem. You had perfect timing... I haven't played with my Oaks (that sounds so wrong!) since I posted that, and popped in to see what had been happening on the forums in the mean time. I would think it is too much of a coincidence, and that it would be overflow. Hopefully I haven't given you wrong info, as I don't have the exact same unit, but for my test, Wire certainly was platform agnostic.

Pete

jwhendy

  • Newbie
  • *
  • Posts: 44
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #5 on: March 25, 2016, 08:54:50 pm »
I admit to having no clue what's going on. I either get nonsense values from particle.variable() using the code above or nothing when trying to merge your code and mine. My dashboard for particle.publish is empty, so I think I'm doing something wrong. I had a hard time figuring out how to work it, as it kept complaining about my variable types (I was trying to just put my int x/y/z values in). I think that's what your sprintf() commands are doing, but I'm not familiar with what that does.

Here's where I ended up, which gives me nothing:

Code: [Select]
#include <Wire.h>
// #include <SPI.h>

// adxl device for wire.h
#define DEVICE (0x53)


// adxl setup
byte _buff[6];
char POWER_CTL = 0x2D;  //Power Control Register
char DATA_FORMAT = 0x31;
char DATAX0 = 0x32; //X-Axis Data 0
char DATAX1 = 0x33; //X-Axis Data 1
char DATAY0 = 0x34; //Y-Axis Data 0
char DATAY1 = 0x35; //Y-Axis Data 1
char DATAZ0 = 0x36; //Z-Axis Data 0
char DATAZ1 = 0x37; //Z-Axis Data 1
uint8_t howManyBytesToRead = 6;

int16_t x = 0;
int16_t y = 0;
int16_t z = 0;

char sz_x[8];
char sz_y[8];
char sz_z[8];

char sz_info[16];

long now = millis();

void setup() {


  pinMode(1, OUTPUT); // LED on oak

  Particle.variable("x", x);
  Particle.variable("y", y);
  Particle.variable("z", z);
  delay(1000);

  // join i2c bus (address optional for master)
  Wire.begin();
  delay(1000);

  // Put the ADXL345 into +/- 4G range by writing the value 0x01 to the DATA_FORMAT register.
  // think 0x00 = 2g, 00x1 = 4g, 00x2 = 8g, and 00x3 = 16g
  //writeTo(DATA_FORMAT, 0x01);

  //Put the ADXL345 into Measurement Mode by writing 0x08 to the POWER_CTL register.
  writeTo(POWER_CTL, 0x08);

  Serial.begin(9600);

}


// the loop routine runs over and over again forever:
void loop() {

  if(millis() - now > 100)
  {

    //read the acceleration data from the ADXL345
    readFrom(DATAX0, howManyBytesToRead, _buff);
    x = (((int)_buff[1]) << 8) | _buff[0];
    y = (((int)_buff[3]) << 8) | _buff[2];
    z = (((int)_buff[5]) << 8) | _buff[4];


    printf(sz_info, "%i", x);
    Particle.publish("x", sz_info);
    memcpy( sz_x, sz_info, 8 );

    now = millis();

  }

  if(millis() - now > 50)
  {
    digitalWrite(1, LOW);
  }

  if(millis() - now < 50)
  {
    digitalWrite(1, HIGH);
  }

}

///////// begin accelerometer reader //////////

void writeTo(byte address, byte val) {
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address);             // send register address
  Wire.write(val);                 // send value to write
  Wire.endTransmission();         // end transmission
}

// Reads num bytes starting from address register on device in to _buff array
void readFrom(byte address, int num, byte _buff[]) {
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address);             // sends address to read from
  Wire.endTransmission();         // end transmission

  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from device

  int i = 0;
  while (Wire.available())        // device may send less than requested (abnormal)
  {
    _buff[i] = Wire.read();    // receive a byte
    i++;
  }

  Wire.endTransmission();         // end transmission
}

////////// end accelerometer reader //////////

Without the use of particle.publish I get values, but they still go to right around 65,400 when I tilt it too much.

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #6 on: April 03, 2016, 01:56:41 am »
Just saw this... I really don't know what's going on there...

As far as the sprintf(szInfo, "%i", AcX); style statements, it is being used to convert from an integer to a character string, as the publish function only accepts Strings or Char strings. It is in the form of output buffer, formatting, input variables. So in this instance, it is taking AcX as the input, formatting it as an integer, and then storing it as a char string in szInfo. The function is used to allow for formatting and 'pretty printing' - you can do a string like "The time is HH:MM AM/PM" with something like (don't quote me... it is probably broken!) sprintf(buffer,"The time is %2i:%2i %s",hour,minute,meridiem); (with the %2i meaning a two digit integer, and the %s meaning a string of characters). Have a look at this if you want to find out more, as printf and sprintf are very handy functions when working with strings, variables and formatting. 

Code: [Select]
Particle.publish(const char *eventName);
Particle.publish(String eventName);

btw: I've just ordered one of those adxl345, so I'll let you know how I go when mine turns up! ;)
« Last Edit: April 03, 2016, 02:13:49 am by pfeerick »

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #7 on: July 31, 2016, 02:13:28 am »
Well, I finally cleared out some of my desk, and remembered to come back and have a look at this further... but do you think I can find the flipping adxl345 sensor!!! I know I have one as I remember thinking when it arrived that it had quite a few extra pins!

Regardless, I was trying a HMC5883L digital compass ("Triple Axis Magnetometer" if you prefer the more proper name!)... and  it worked ok with the suggested library ... but only once I made one change to the "simple" example sketch... I added Wire.begin(0,2) before initialising the compass library object.

 I know in the library objects own begin function it calls Wire.begin(), but on the ESP8266 Arduino ref docs, it says you need to use the altered form of Wire.begin(SDA pin, SCL pin) to tell the library which pins are to be used as the I2C pins. So it appears as long as you issue the command before anything else does, it sticks. It made the difference between the magnetometer being detected and not detected on the Oak, so it definitely work in that instance. That would be my first change from the code used above once I find this pesky sensor...

PeterF

  • Hero Member
  • *****
  • Posts: 881
Re: Assistance with reading an accelerometer with I2C/wire.h
« Reply #8 on: August 01, 2016, 03:07:29 am »
Ok, so after tossing half the place, the sensor turned up! :D

I got the OP example sketch working ok with a Arduino Nano, so the sensor works and is giving meaningful data.

Move over to the Oak, and I get the nonsense readings. Changing the Wire line didn't make any difference there. So I thought a bit more after looking at how the data is processed, and realised that int doesn't mean int on the Oak like it does on the Arduino. On the AVR (Uno, Nano, etc) boards, it really means uint8_t - an 8 bit integer. But on the Oak/ESP8266, it means a 32 bit integer. And since there is some bitwise shifting going on, maybe that was making things screwy? Changed the variable declarations to uint8_t, and it works! :D Bingo!

Attached is the code I have working. It has conditional compilation defines to allow it to work on Uno/Nano and Oak without any changes.

Code: [Select]
/************************************************************************
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU License V2.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License, version 2 for more details

  Code based on work by Jens C Brynildsen as acknlowledged below.

  Modified by Peter Feerick 1/AUG/2016 for compability with
  the Digistump Oak and Particle

  Bare bones ADXL345 i2c example for Arduino 1.0
  by Jens C Brynildsen <http://www.flashgamer.com>
  This version is not reliant of any external lib
  (Adapted for Arduino 1.0 from http://code.google.com/p/adxl345driver)

  Demonstrates use of ADXL345 (using the Sparkfun ADXL345 breakout)
  with i2c communication. Datasheet:
  http://www.sparkfun.com/datasheets/Sensors/Accelerometer/ADXL345.pdf
  If you need more advanced features such as freefall and tap
  detection, check out:
  https://github.com/jenschr/Arduino-libraries
***********************************************************************/

/* Connections

   Uno/Nano
   Gnd      -  GND
   3.3v     -  VCC
   3.3v     -  CS
   Analog 4 -  SDA
   Analog 5 -  SCL

   Oak
   Gnd      -  GND
   3.3v     -  VCC
   3.3v     -  CS
   P0       -  SDA
   P1       -  SCL
*/
#include <Wire.h>

// adxl device for wire.h
#define DEVICE (0x53)

//put Oak in SEMI_AUTOMATIC mode to enable reliable variable registration
#ifdef ARDUINO_ESP8266_OAK
SYSTEM_MODE(SEMI_AUTOMATIC)
#endif

// adxl setup
byte _buff[6];
char POWER_CTL = 0x2D;  //Power Control Register
char DATA_FORMAT = 0x31;
char DATAX0 = 0x32; //X-Axis Data 0
char DATAX1 = 0x33; //X-Axis Data 1
char DATAY0 = 0x34; //Y-Axis Data 0
char DATAY1 = 0x35; //Y-Axis Data 1
char DATAZ0 = 0x36; //Z-Axis Data 0
char DATAZ1 = 0x37; //Z-Axis Data 1
uint8_t howManyBytesToRead = 6;

uint8_t x = 0;
uint8_t y = 0;
uint8_t z = 0;

long now = millis();

void setup()
{
#ifdef ARDUINO_ESP8266_OAK
  Particle.variable("x_val", x);
  Particle.variable("y_val", y);
  Particle.variable("z_val", z);

  if (Particle.connected() == false) {
    Particle.connect();
  }

  Particle.publish("adxl345_test sketch starting");
#endif

  // join i2c bus (address optional for master)
  // Oak/ESP8266 needs SDA & SCL pins defined, Arduino doesn't
#ifdef ARDUINO_ESP8266_OAK
  Wire.begin(0, 2);
#else
  Wire.begin();
#endif

  Serial.begin(9600);
  Serial.println("adxl345_test sketch starting");

  // Put the ADXL345 into +/- 4G range by writing the value 0x01 to the DATA_FORMAT register.
  // 0x00 = 2g, 0x01 = 4g, 0x02 = 8g, and 0x03 = 16g
  writeTo(DATA_FORMAT, 0x01);

  //Put the ADXL345 into Measurement Mode by writing 0x08 to the POWER_CTL register.
  writeTo(POWER_CTL, 0x08);
}

// the loop routine runs over and over again forever:
void loop()
{
  if (millis() - now > 250)
  {
    //read the acceleration data from the ADXL345
    readFrom(DATAX0, howManyBytesToRead, _buff);
    x = (((int)_buff[1]) << 8) | _buff[0];
    y = (((int)_buff[3]) << 8) | _buff[2];
    z = (((int)_buff[5]) << 8) | _buff[4];

    // for debug when using with arduino
    Serial.print("X = ");
    Serial.print(x);
    Serial.print(", Y = ");
    Serial.print(y);
    Serial.print(", Z = ");
    Serial.println(z);

    now = millis();
  }
}

///////// begin accelerometer reader //////////

void writeTo(byte address, byte val)
{
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address);             // send register address
  Wire.write(val);                 // send value to write
  Wire.endTransmission();         // end transmission
}

// Reads num bytes starting from address register on device in to _buff array
void readFrom(byte address, int num, byte _buff[])
{
  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.write(address);             // sends address to read from
  Wire.endTransmission();         // end transmission

  Wire.beginTransmission(DEVICE); // start transmission to device
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from device

  int i = 0;
  while (Wire.available())        // device may send less than requested (abnormal)
  {
    _buff[i] = Wire.read();    // receive a byte
    i++;
  }

  Wire.endTransmission();         // end transmission
}

////////// end accelerometer reader //////////