BMA180 outputs same values regardless of motion

Hi,

I have a working BMA180, tested with a GUI of a multicopter flight controller to check the values (multiwii GUI)

I'm trying to write a code to read it in and export values over serial. I used some tutorials from Geeetech :
http://www.geeetech.com/wiki/index.php/BMA180_Triple_Axis_Accelerometer_Breakout

But when i do it, i keep having the same values whenever i move the sensor.

Here is my version of the code. I was hoping you'd give me a hint on what i'm doing wrong. I did similar codes with ITG3205 and i had no problem, although its datasheet was kinda clearer than the BMA180's, so addressing the registers and reading the data is a bit different. Might be why i got confused.

//BMA180 triple axis accelerometer sample code//
//
#include <Wire.h> 
#define BMA180 0x40  //address of the accelerometer
#define PWR 0x0D
#define BWTCS 0x20 
#define TCOZ 0X30
#define RANGE 0X35
#define DATA 0x02
//

int coords[3] = {0,0,0};

void setup()
{
  Serial.begin(115200); 
 Wire.begin(); 
 Acc_Init(); 
 Serial.println("Sensors have been initialized");
}

//

void loop() 
{ 
 if (Serial.available() > 0) 
 {
 Acc_Read(coords); 
 Serial.print("x=");
 Serial.print(coords[0]);
 Serial.print("mg"); 
 
 Serial.print(",y=");
 Serial.print(coords[1]);
 Serial.print("mg"); 
 
 Serial.print(",z=");
 Serial.print(coords[2]);
 Serial.println("mg"); 
 
 delay(300); // slow down output
 }
}

void Acc_Init() 
//
{ 
 byte temp[1];
 byte temp1;
 
  delay(10);
  writeTo(BMA180, PWR, 1<<4); 
  delay(5);
  readFrom(BMA180, BWTCS, 1 ,temp);  
  temp1=(temp[0]&0x0F) | (0x00 << 4); // set low pass filter to 10Hz (bits value = 0000xxxx)
  writeTo(BMA180,TCOZ,temp1);
  delay(5);
  readFrom(BMA180, TCOZ, 1 ,temp);  
  temp1=(temp[0]&0xFC) | 0x00;        // set mode_config to 0
  writeTo(BMA180,TCOZ,temp1);
  delay(5);
  // range 8g 
  readFrom(BMA180, RANGE, 1 ,temp);  
  temp1=(temp[0]&0xF1) | (0x05 << 1);
  writeTo(BMA180,RANGE,temp1);
  delay(5);
}
//
void Acc_Read(int coordinates[]) 
{ 
 // read in the 3 axis data, each one is 14 bits 
 // print the data to terminal 
 byte result[6];
 readFrom(BMA180, DATA, 6, result);
 
 coordinates[0]= (( result[0] | result[1]<<8)>>2) ;
 coordinates[1]= (( result[2] | result[3]<<8)>>2);
 coordinates[2]= (( result[4] | result[5]<<8)>>2);
 
}
//
//---------------- Functions--------------------
//Writes val to address register on ACC
void writeTo(int DEVICE, byte address, byte val) 
{
  Wire.beginTransmission(DEVICE);   //start transmission to ACC
  Wire.write(address);               //send register address
  Wire.write(val);                   //send value to write
  Wire.endTransmission();           //end trnsmisson
}



//reads num bytes starting from address register in to buff array
void readFrom(int DEVICE, byte address , int num ,byte buff[])
 {
 Wire.beginTransmission(DEVICE); //start transmission to ACC
 Wire.write(address);            //send reguster address
 Wire.endTransmission();        //end transmission
 
 Wire.beginTransmission(DEVICE); //start transmission to ACC
 Wire.requestFrom(DEVICE,num);  //request 6 bits from ACC
 
 int i=0;
 while(Wire.available())        //ACC may abnormal
 {
 buff[i] =Wire.read();        //receive a byte
 i++;
 }
 Wire.endTransmission();         //end transmission
 }

The values i get are :

x=-640mg,y=-704mg,z=-768mg
x=-640mg,y=-704mg,z=-768mg
x=-640mg,y=-704mg,z=-768mg

and so on...
whatever the motion.

(0x00 << 4) will always be zero so oring it has no effect

temp1=(temp[0]&0xFC) | 0x00; <=> temp1 = (temp[0] & 0xFC);

Please use spaces to make formulas more readable, you do that also with words :wink:

temp1 = ( temp[0] & 0xF1 )  |  ( 0x05  <<  1 );

Strange formuls again...
Do you have a link to the datasheet?

  • page number where you found this formula?
 readFrom(BMA180, TCOZ, 1 ,temp);  
  temp1=(temp[0]&0xFC) | 0x00;        // set mode_config to 0
  writeTo(BMA180,TCOZ,temp1);
  delay(5);
  // range 8g 
  readFrom(BMA180, RANGE, 1 ,temp);  
  temp1=(temp[0]&0xF1) | (0x05 << 1);           <<<<<<<<<<<<< here you overwrite temp1  why?
  writeTo(BMA180,RANGE,temp1);

This is faulty

//reads num bytes starting from address register in to buff array
void readFrom(int DEVICE, byte address , int num ,byte buff[])
 {
 Wire.beginTransmission(DEVICE); //start transmission to ACC
 Wire.write(address);            //send reguster address
 Wire.endTransmission();        //end transmission
 
 Wire.beginTransmission(DEVICE); //start transmission to ACC
 Wire.requestFrom(DEVICE,num);  //request 6 bits from ACC
 
 int i=0;
 while(Wire.available())        //ACC may abnormal
 {
 buff[i] =Wire.read();        //receive a byte
 i++;
 }
 Wire.endTransmission();         //end transmission
 }

should be

//reads num bytes starting from address register in to buff array
int readFrom( int device, byte regAddress , int num, byte buff[] )
 {
   Wire.beginTransmission(DEVICE); 
   Wire.write(regAddress);            
   Wire.endTransmission();\
 
   Wire.requestFrom(DEVICE,num);
   int i = 0;
  for (i=0; i< Wire.available(); i++) 
  {
    buff[i] = Wire.read();
  }
  return i;
 }

give it a try

Thank you for your reply.

I Just tried changing readFrom as you advised, i assume returning " i " is just for debug ?
Anyway, still the same problem, output triplet values stay the same.

datasheet :

readFrom(BMA180, RANGE, 1 ,temp);  
  temp1= ( temp[0] & 0xF1 ) | ( 0x05 << 1 );

You're mentioning that they overwrite temp1... not really. It's read in temp, then temp1 manipulates temp. temp1 is just a temporary variable to setup the sensor. But maybe you meant something else ?

This is supposed to set the range to 8G. I don't understand why we need to read first using readFrom and "extract" the value at the register, and put it in temp. Why not setting the value directly just calling writeTo with the needed value that sets that range. Why using readFrom at all there ? I don't get it... With the ITG3200/3205, one only needs to write for a setup. Instead, with the BMA180, they read, tweak whatever values is read, and then write back to it / overwrite (maybe that's what you meant by overwriting ??? your comment makes more sense to me if i see it that way)

Got the codes from :
http://www.geeetech.com/wiki/index.php/BMA180_Triple_Axis_Accelerometer_Breakout

But I tried another initialization (the one i sent), using one that proved to be working, the multiwii 2.2 sensor reading code :
(Google Code Archive - Long-term storage for Google Code Project Hosting.)

Anyway, this initialization was copy pasted from the multiwii 2.2 code, which has a lot of weird formulas that sometimes do nothing at all, i agree. But i have a working code with this initialization, when i extract the only part that reads in the BMA180 values. So, i know the initialization works, but for reading the values, they're not using the wire library, and i'm too much a noob for now to understand the quite low level i2c and serial reading routines they used. And i'd like to write my own code, not blindly copy things. The point is to learn too...

Anyway, i also tried the one in Geeetech below at first, I had the same outcome.

void Acc_Init() 
{ 
 byte temp[1];
 byte temp1;
  //
  writeTo(BMA180, RESET, 0xB6);
  //wake up mode
   delay(10);
  writeTo(BMA180, PWR, 0x10);
   delay(10);
  // low pass filter,
  readFrom(BMA180, BW, 1, temp);
  temp1 = temp[0] & 0x0F;
  writeTo(BMA180, BW, temp1);   
  delay(10);
  // range +/- 2g 
  readFrom(BMA180, RANGE, 1 ,temp);  
  temp1 = ( temp[0] & 0xF1 ) | 0x04;
  writeTo(BMA180, RANGE, temp1);
 delay(10);
}

Thanks again for helping out.

i assume returning " i " is just for debug ?

yes, to see how many bytes were received.

You're mentioning that they overwrite temp1... not really. It's read in temp, then temp1 manipulates temp. temp1 is just a temporary variable to setup the sensor. But maybe you meant something else ?

You're right, I missed the writeTo , sorry

This is supposed to set the range to 8G. I don't understand why we need to read first using readFrom and "extract" the value at the register, and put it in temp. Why not setting the value directly just calling writeTo with the needed value that sets that range. Why using readFrom at all there ?

This is probably done to keep bits in the registers that should be the same.

I have no time to dive in the datasheet tonight - have to wake up quite early tomorrow :wink:

found some links that might get you further

did you note the sensor is 3.3Volt ?

(bedtime)

thanks for the links, i read few of them a few days go.
The sensor is hooked up with the ITG3205 on a 6 dof sensor that can support 3.3V and 5V with an onboard regulator.
The working code i have from multiwii is doing fine with the 5V supply. Never had any i2c errors, nor data losses. But maybe it's setting something that is not set with the other smaller code (the geeetech's one). But in the multiwii code, I didn't see anything related to the voltage management in the initialization of the sensor though, so i didn't think it was issue.

robtillaart:
found some links that might get you further

did you note the sensor is 3.3Volt ?

(bedtime)

So, i tried every link ! :sweat_smile:

First one was a bit outdated. A few function in the FreeIMU BMA180 library was changed, i.e, readAccel() was changed to need inputs. So, i changed all that. All i got was zeros for all X, Y , and Z.

The 2nd link gave a library on which i just had to change the Wire.receive => read , Wire.send => write, and change the include.
Same issue. All i got was zeros for all X, Y , and Z.

Already tried the 3rd link before my 1st post, it gave the exact same issue of my 1st post. Had non-zero values, but keeps outputting the same triplet coordinates.

Here is the code i used for the 1st and 2nd link (with a little change with the readAccel() that needed inputs in the 1st link) :

#include <Wire.h>
#include <bma180.h>

BMA180 bma180;
char startcommand = 69 ; // ASCII – E, dec: 69,
char command ; //Command received from PC
float timeStep = 0.2; //200ms. Need a time step value for integration of gyro angle from angle/sec
unsigned long timer;
void setup()
{
  Wire.begin();
  Serial.begin(38400);
  bma180.SoftReset();
  bma180.enableWrite();
  bma180.SetFilter(bma180.F10HZ);
  bma180.setGSensitivty(bma180.G15);
  bma180.SetSMPSkip();
  bma180.SetISRMode();
  bma180.disableWrite();
  delay(100);
  Serial.println("Press E to start ");
  while ( command != startcommand){
    if (Serial.available() > 0) { // wait for 1 byte with start command
      command = Serial.read();
    }
    else {
      delay(50);
    }
  }
}
void loop()
{
  timer = millis(); //get a start value to determine the time the loop takes
  // Using x y and z from accelerometer, calculate x and y angles
  bma180.readAccel();
  Serial.print("X = ");
  Serial.print(bma180.getXValFloat());
  Serial.print(" Y = ");
  Serial.print(bma180.getYValFloat());
  Serial.print(" Z = ");
  Serial.print(bma180.getZValFloat());
  Serial.println();
  delay(20);
  timer = millis() - timer;
  timer = (timeStep * 1000) - timer;
  delay(timer); 
}

I was also wondering how the code knows the address of the device. I thought it was done internally in the bma180.cpp :

BMA180::BMA180()
{
    address=0x40;
}

Since i wasn't sure if that was a function definition or an actual instruction executed and giving the device address to the rest of the program (i'm still rusty on my c++), i also tried to use

SetAddress(0x40)

in the setup() of my sketch, but that didn't change anything.

So, these library gave me worse outcome. In the sketch of my first post, i had the expected value ( 1 g on the Z axis ), but they just kept outputting the same values afterwards.

I will scrutinize the little piece of working code copied from multiwii and try to understand why it's working in there, and not with the others. I'll try to paste the working-bare-minimum of it, maybe someone can help me understand what i was missing.

Some progress.

I confirm that the problem does not come from the initialization. It comes from the low level i2c reading functions.
To check that, i kept the initialization as it is. And i copy-pasted only the i2c reading functions that are used on the main loop. It worked. I only changed the function "Acc_Read(coords)" to "ACC_getADC()", which calls these "custom-made" i2c functions.

So, comparing the wire-library function and this, is still too tricky at my current level. But i'd like to understand why the wire-library didn't work, and why this one does.

Below is my new sketch, followed by their "custom-made" functions used for reading/writing over i2c :

//BMA180 triple axis accelerometer sample code//
//
#include <Wire.h> 
#define BMA180_ADDRESS 0x40  //address of the accelerometer
#define PWR 0x0D
#define BWTCS 0x20 
#define TCOZ 0X30
#define RANGE 0X35
#define DATA 0x02
//

byte mode = 1;
unsigned long dt = 0L;
unsigned long strtmicros = 0L  ;
unsigned long duration = 10000000L;

static int16_t  accADC[3];
static int16_t  i2c_errors_count = 0;

void setup()
{
  Serial.begin(115200); 
  Wire.begin(); 
  Acc_Init(); 
  Serial.print('a'); // send to PC

  char a = 'b';
  while(a !='a')
  {
    // wait for character from PC
    a = Serial.read();
  }
}

//

void loop() 
{ 
  if (Serial.available() > 0) 
  {
    mode = Serial.read(); // check if there is a request from PC for gyro values

    if (mode == 1 );
    {
      mode = 0;
      strtmicros = micros();
      dt = 0L;
      while(dt < duration)
      {   
        //Acc_Read(coords);  
        ACC_getADC();
        //sendData(coords);
        sendData(accADC);
        dt = micros() - strtmicros;
      }
    }
  }
}

void sendData(int CoordT[])
{
  Serial.print(CoordT[0]); Serial.print(" ");
  Serial.print(CoordT[1]); Serial.print(" ");
  Serial.println(CoordT[2]);
}

//---------------- Functions--------------------

void Acc_Init() 
//
{ 
  byte temp[1];
  byte temp1;

  delay(10);
  writeTo(BMA180_ADDRESS , PWR , 1<<4); 
  delay(5);
  readFrom(BMA180_ADDRESS , BWTCS , 1 ,temp);  
  temp1=(temp[0] & 0x0F) | (0x00 << 4); // set low pass filter to 10Hz (bits value = 0000xxxx)
  writeTo(BMA180_ADDRESS, TCOZ, temp1);
  delay(5);
  readFrom(BMA180_ADDRESS , TCOZ , 1 ,temp);  
  temp1=(temp[0] & 0xFC) | 0x00;        // set mode_config to 0
  writeTo(BMA180_ADDRESS , TCOZ, temp1);
  delay(5);
  // range 8g 
  readFrom(BMA180_ADDRESS , RANGE , 1 , temp);  
  temp1=(temp[0] & 0xF1) | (0x05 << 1);
  writeTo(BMA180_ADDRESS , RANGE , temp1);
  delay(5);
}

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

//reads num bytes starting from address register in to buff array
void readFrom(int DEVICE, byte address , int num ,byte buff[])
{
  Wire.beginTransmission(DEVICE); //start transmission to ACC
  Wire.write(address);            //send reguster address
  Wire.endTransmission();        //end transmission

    Wire.requestFrom(DEVICE,num);
  int i = 0;
  for (i=0; i< Wire.available(); i++) 
  {
    buff[i] = Wire.read();
  }

}

And the i2c-reading/writing functions used :

#define ACC_ORIENTATION(X, Y, Z)  {accADC[0]  = -X; accADC[1]  = -Y; accADC[2]  = Z;}
#define I2C_SPEED 400000L     //100kHz normal mode, this value must be used for a genuine WMP


uint8_t rawADC[6];
static uint32_t neutralizeTime = 0;

void i2c_rep_start(uint8_t address) {
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN) ; // send REPEAT START condition
  waitTransmissionI2C();                       // wait until transmission completed
  TWDR = address;                              // send device address
  TWCR = (1 << TWINT) | (1 << TWEN);
  waitTransmissionI2C();                       // wail until transmission completed
}

void i2c_stop(void) {
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
  //  while(TWCR & (1<<TWSTO));                // <- can produce a blocking state with some WMP clones
}

void i2c_write(uint8_t data ) {
  TWDR = data;                                 // send data to the previously addressed device
  TWCR = (1 << TWINT) | (1 << TWEN);
  waitTransmissionI2C();
}

uint8_t i2c_read(uint8_t ack) {
  TWCR = (1 << TWINT) | (1 << TWEN) | (ack? (1 << TWEA) : 0);
  waitTransmissionI2C();
  uint8_t r = TWDR;
  if (!ack) i2c_stop();
  return r;
}

uint8_t i2c_readAck() {
  return i2c_read(1);
}

uint8_t i2c_readNak(void) {
  return i2c_read(0);
}

void waitTransmissionI2C() {
  uint16_t count = 255;
  while (!(TWCR & (1 << TWINT))) {
    count--;
    if (count==0) {              //we are in a blocking state => we don't insist
      TWCR = 0;                  //and we force a reset on TWINT register
      neutralizeTime = micros(); //we take a timestamp here to neutralize the value during a short delay
      i2c_errors_count++;
      break;
    }
  }
}

size_t i2c_read_to_buf(uint8_t add, void *buf, size_t size) {
  i2c_rep_start((add << 1) | 1);  // I2C read direction
  size_t bytes_read = 0;
  uint8_t *b = (uint8_t*)buf;
  while (size--) {
    /* acknowledge all but the final byte */
    *b++ = i2c_read(size > 0);
    /* TODO catch I2C errors here and abort */
    bytes_read++;
  }
  return bytes_read;
}

size_t i2c_read_reg_to_buf(uint8_t add, uint8_t reg, void *buf, size_t size) {
  i2c_rep_start( add << 1); // I2C write direction
  i2c_write(reg);        // register selection
  return i2c_read_to_buf(add, buf, size);
}

void i2c_getSixRawADC(uint8_t add, uint8_t reg) {
  i2c_read_reg_to_buf(add, reg, &rawADC, 6);
}


void ACC_getADC () {
  TWBR = ((F_CPU / I2C_SPEED) - 16) / 2;  // Optional line.  Sensor is good for it in the spec.
  i2c_getSixRawADC(BMA180_ADDRESS,0x02);
  //usefull info is on the 14 bits  [2-15] bits  /4 => [0-13] bits  /4 => 12 bit resolution
  ACC_ORIENTATION( ((rawADC[1]<<8) | rawADC[0])>>4 ,
                   ((rawADC[3]<<8) | rawADC[2])>>4 ,
                   ((rawADC[5]<<8) | rawADC[4])>>4 );
 
}