I2C implementation on a 328P with ITG3200 and BMA180

Hello, I'm in the middle of a project where I have to manipulate typical PWM R/C signals, and be able to modify them according to sensor data.

So far I have managed to make that my code read and write PWM using pinChangeInterrupt and Servo libraries but I have failed miserably in my efforts to try to read the sensors using the I2C bus.

Before asking here I tried to search for info about this on the forum but after many hours of research I got to no clear conclusions so I've resolved to ask directly if someone can help me.

I am using a Multiwii 328P board, which has integrated the following sensors:
ITG3205 + BMA180 + BMP085 + HMC5583L

All the code that refers to the I2C bus and sensors, I have been getting 'inspiration' from the original Multiwii code:

I attach my custom code.

It compiles without errors and when I flash it to my 328P board, the servos do work in accordance with the PWM that comes out of an R/C receiver that I control with an R/C transmitter... but I don't seem to be able to get any readings from the sensors to affect the movement of the servos.

Please, help!

PWMRCIIC.ino (4.13 KB)

Sensors.cpp (17.5 KB)

Sensors.h (918 Bytes)

I have no idea, ask the people who provided the code.

One approach would be to create a minimal sketch which demonstrates the functioning of the sensor you are attempting to use.

Include nothing more that is necessary to execute this statement:

Gyro_getADC ();

Then print out the value of the variables returned:

gyro_x
gyro_y
gyro_z

Then work from there.

If you don't get any further, one obvious thing to verify is the I2C address of the sensor.

6v6gt:
One approach would be to create a minimal sketch which demonstrates the functioning of the sensor you are attempting to use.

Well, I did something like that and after much tweaking, managed to initialize and get some readings from the ITG3205 gyro sensor and BMA180 accelerometer.

After that I tried to transform the readings into something with units that had physical meaning and kind of succeeded with the gyro but the accelerometer is not working right...

I have set it up for +/- 8G range and according to datasheet, this yields a resolution of 0.99mG/LSB.

Thing is when I code it, the serial readings are about 200mG, when they should be about a 1000mG (1G) when sitting static. I don't know what the problem is, maybe someone could help?

Also found a remark on the datasheet (I attached a screenshot in .png) that warns about offset-shits when configuring the acc for 8G range... has anyone already dealed with a similar issue?

What would be the right way to callibrate the mentioned offset-shift?

I also attach the code:

IICtest.ino (7.25 KB)

What's the reading if you turn the sensor upside down?

Hadn't thought about that!

It reads (z axis) about 200 upright, -300 inverted.

Correct me if I'm wrong but being this the case, it doesn't look like an offset issue.

To be honest, I still don't understand what my library does behind the scene. Assuming a +-2g range, I get raw values of about +8000 and -8000 for the z axis, after excluding the gravity removal code. If I tilt the sensor, I get about the same values in the other axes.

Perhaps your library also attempts to remove gravity from the measured accelerations?
If so, do you get higher values when moving the sensor quickly up and down, in order to add linear acceleration?

I am using no particular library to process the raw data... only wire.h to access the I2C bus but apart from that, I'm just taking the values that the BMA180 outputs (Assuming 8G range) and multiplying them by 0.99 (Resolution according to datasheet).

I quite don't get what library are you implying that I use that attempts to remove gravity from the readings.

My thoughts on possible failure points:

  • The bitshifts used to recover the 2 byte (MSB, LSB) data from the sensors is not coded properly
  • The initialization of the BMA180 failing for an unknown reason and then the range is 2G instead of 8G
  • 8G range offset shifts (???)

Help!

To check the byte order, you can output the raw z value (in hex). Then tilt the sensor a bit, to find out which byte changes faster, that's the LSB.

I more-or-less resolved the above but now I'm trying to get the most accurate possible readings with both gyro and accelerometer and I am finding trouble getting them to match properly...

Accelerometer readings appear to be ok but gyro reading doesn't quite match the angle measurement properly and it drifts way too quick over time (Even though I am calibrating them each time I power up).

Also sometimes the values just jump totally out of control. I'm kind of lost. Could anoyone take a look at the code and suggest how to proceed?

Code attached below:

IICtest.ino (12.3 KB)

Hi again,
Let's say that I have already managed to calculate the angle estimation separately with the accelerometers using atan2 function and with the gyros through integration.

Then I am trying to implement a complementary filter in the way described on the article from the link below:

http://www.pieter-jan.com/node/11

My code for the filter is this:

// Complementary filter
angleX = angle_x*a + (1-a)angle_gx; // 2% accelerometer, 98% gyro
angleY = angle_y
a + (1-a)*angle_gy;

Where
-> angleX and angleY are the comp. filter outputs,
-> angle_x and angle_y are the acc measured angles
-> angle_gx and angle_gy are the gyro meassured angles
-> 'a' is a float variable that I use to change the weights of gyro and acc on the complementary filter.

Gyro angles are calculated like this:

gx = gY/14.375f; // [º/s] (14.375 LSB / 1º/s)
gy = gX/14.375f; // [º/s]
gz = -gZ/14.375f; // [º/s]

// Integrator - angle, º/sec
angle_gx += gx * (cycleTime/1e6); // [º/s]*[ s ] = [º]
angle_gy += gy * (cycleTime/1e6); // [º]
angle_gz += gz * (cycleTime/1e6); // [º]

Acc angles are calculated like this:

// Board Orientation:
ax=-(((float)aY)*0.99f/1000.0); // [G] (1 LSB / 0.99mg)
ay=-(((float)aX)*0.99f/1000.0); // [G]
az=((float)aZ)*0.99f/1000.0; // [G]

ax2 = (float)ax*(float)ax;
ay2 = (float)ay*(float)ay;
az2 = (float)az*(float)az;

angle_x = -(atan2(-ay, az)*180.0)/M_PI;
angle_y = -(atan2(ax, sqrt(ay2 + az2))*180.0)/M_PI;

I take that according to the theory, if I weigh the acc like 98% and 2% the gyro, the angle estimations of the filter (angleX and angleY) should tend to the accelerometer readings (angle_x and angle_y) over time but THEY DO NOT in my code. Trying to find out what did I do wrong. Hopefully someone can help.

Again, full code attached below:

IICtest.ino (12.5 KB)

angleX = angle_x*a + (1-a)*angle_gx; // 2% accelerometer, 98% gyro

Shouldn't it be 98% accelerometer (stable!) and 2% gyro?

DrDiettrich:
Shouldn't it be 98% accelerometer (stable!) and 2% gyro?

According to theory, nope. (Although I've tried with diverse 'a' values to check if that fixed it).

The idea is that in a non-inertial frame, the accelerometers will read accelerations other than gravity itself so the angle calculations cease to be valid. These are only accurate while the reference is not accelerating (other than by earth's gravity pull).

That way, the gyro should be getting the angle right most of the time but the gyro-drift get corrected over time, with the accelerometers signal.

Thing is, for example, if I leave my board still after having moved it for a while, the accelerometer angle readings quickly go to 0.0º (flat) but the angle by the complementary filter doesn't go back to 0.0º.

It should correct the drift over time but does not, that's what I don't understand.

So, for example, here goes an example of the data I get printed through the serial port:

Values presented are for a single axis (x):

Angle integrated by gyro - angle obtained by atan2 (accel) - angle computed by complementary filter with a=0.5
cycle time in microseconds

60.53 64.18 62.36
25996

60.67 63.1 61.84
26012

60.95 60.86 60.91
24936

61.26 61.21 61.23
26024

61.53 63.69 62.61
25984

61.71 64.55 63.13
26008

61.82 65.59 63.70
26000

62.1 64.1 63.1
25992

At first the gyro has little drift and everything looks accurate but then as the drift of the gyro grows over time the complementary filter doesn't do what it should:

(This readings are with the board stationary).

36.96 0.11 18.42
24956

36.96 0.17 18.39
24972

36.96 0.28 18.33
24956

36.96 0.23 18.36
24960

36.96 0.34 18.31
24972

Notice that with an 'a' of 0.5 it is almost acting as if it calculated the arithmetic average between the gyro and acc. readings, like (angle gyro+angle acc)/2.

I'm confused. :confused:

Well, it works.

:smiley:

RC4ever:
Hadn't thought about that!

It reads (z axis) about 200 upright, -300 inverted.

Correct me if I'm wrong but being this the case, it doesn't look like an offset issue.

Correction.. if one way you get 200 upside down shouldn't you get -200, not -300?
Offset of -50 I'd say.
Add 50 to 200 = 250
Add 50 to -300 = -250
Correct me if I'm wrong..

I agree that using 14bit conversion and +/- 8G, you should have 1024 for 1g.

It looks like you are still in 12bit mode as it will give 256 for 1g.

Tom.. :slight_smile:

Great :slight_smile:

How did you cure the before mentioned problem?

DrDiettrich:
Great :slight_smile:

How did you cure the before mentioned problem?

TomGeorge:
Correction.. if one way you get 200 upside down shouldn't you get -200, not -300?
Offset of -50 I'd say.
Add 50 to 200 = 250
Add 50 to -300 = -250
Correct me if I'm wrong..

I agree that using 14bit conversion and +/- 8G, you should have 1024 for 1g.

It looks like you are still in 12bit mode as it will give 256 for 1g.

Tom.. :slight_smile:

To be fair, I don't recall at which point did I manage to solve it (Not much serious on my behalf, sorry) but I made some changes and suddenly it was working. I attach the final code relative to the accelerometer just for reference.

As for the complementary filter, I was implementing it wrong as:

Angle_filter = a*(angle_gy) + (1-a)*angle_acc

Whereas it had to be done like this:

Angle_filter = a*(Angle_filter[i-1]+gy_rate*dt) + (1-a)*angle_acc
[/center]
Acc code:
```
*#define ACC_ADDR 0x40    // Accelerometer address (40),        binary = 01000000
#define REGADDR_ACC 0x02 // Accelerometer registry address (2), binary = 00000010
#define ACC_1G 1010

/* ACCELEROMETER */
int16_t off_ax = 0, off_ay = 0, off_az = 0;      // [LSB] Accelerometer offsets
int16_t aX = 0, aY = 0, aZ = 0;                  // [LSB] Acc readings in sensor axis
double ax = 0, ay = 0, az = 0, ax2 = 0, ay2 = 0, az2 = 0, angle_x = 0, angle_y = 0;
// [mg],[mg^2],[º] Acc readings, units converted, in board axis
float a = 0.1f;                                  // Complementary filter weight parameter
double angleX = 0, angleY = 0, angleZ = 0;      // Complementary filter calculated angles

uint32_t currentTime = 0;
uint16_t previousTime = 0;
uint16_t cycleTime = 0;

void initAcc () {
  /*****************************************
  * BMA180
  * Set to:
  * 8G Range -> ADC Resolution: 0.99 [mg/LSB]
  * Low Pass Filter = 10Hz
  ******************************************/
  byte buff2[1];
  delay(10);
  // Default range 2G: 1G = 4096 unit.
  writeTo(ACC_ADDR,0x0D,0x10); // register: ctrl_reg0  -- value: set bit ee_w to 1 to enable writing
  delay(5);
  readFrom(ACC_ADDR, 0x20, 1, buff2);
  uint8_t control = buff2[0];
  control = control & 0x0F;          // save tcs register
  //control = control | (0x01 << 4); // register: bw_tcs reg: bits 4-7 to set bw -- value: set low pass filter to 20Hz
  control = control | (0x00 << 4);  // set low pass filter to 10Hz (bits value = 0000xxxx)
  writeTo(ACC_ADDR, 0x20, control);
  delay(5);
  readFrom(ACC_ADDR, 0x30, 1, buff2);
  control =  buff2[0];
  control = control & 0xFC;        // save tco_z register
  control = control | 0x00;        // set mode_config to 0
  writeTo(ACC_ADDR, 0x30, control);
  delay(5);
  readFrom(ACC_ADDR, 0x35, 1, buff2);
  control =  buff2[0];
  control = control & 0xF1;        // save offset_x and smp_skip register
  control = control | (0x05 << 1); // set range to 8G -> ADC Resolution: 0.99 [mg/LSB]
  writeTo(ACC_ADDR, 0x35, control);
  delay(5);
}

void getAccData()
{

/**************************************
  Accelerometer BMA180 I2C
  registers:
  x axis MSB = 0x03, x axis LSB = 0x02
  y axis MSB = 0x05, y axis LSB = 0x04
  z axis MSB = 0x07, z axis LSB = 0x06
  *************************************/
 
  readFrom(ACC_ADDR, REGADDR_ACC, nBytes, buff); // Reads nBytes from  BMA180 acc. reg. address onwards and stores them in buff
  //usefull info is on the 14 bits  [2-15] bits  /4 => [0-13] bits  /4 => 12 bit resolution

aX = ((((buff[1] << 8) | buff[0])>>2) - off_ax); // [LSB]
  aY = ((((buff[3] << 8) | buff[2])>>2) - off_ay); // [LSB]
  aZ = ((((buff[5] << 8) | buff[4])>>2) - off_az); // [LSB]

// Board Orientation:
  ax=-(((float)aY)*0.99f/1000.0); // [G] (1 LSB / 0.99mg)
  ay=-(((float)aX)*0.99f/1000.0); // [G]
  az=((float)aZ)*0.99f/1000.0;    // [G]

ax2 = (float)ax*(float)ax;
  ay2 = (float)ay*(float)ay;
  az2 = (float)az*(float)az;

angle_x  = -(atan2(-ay, az)*180.0)/M_PI;
  angle_y = -(atan2(ax, sqrt(ay2 + az2))180.0)/M_PI;
   
  }

* *
*void setup()
{

Wire.begin();
  delay(2000); // To allow board to settle down prior to calibration
 
  initAcc();
 
  int16_t oax = 0;
  int16_t oay = 0;
  int16_t oaz = 0;
  int16_t sum_oax = 0;
  int16_t sum_oay = 0;
  int16_t sum_oaz = 0;
 
  for(int i=1;i<201;i++)
  {
    readFrom(ACC_ADDR, REGADDR_ACC, nBytes, buff);
    // Reads nBytes from  BMA180 acc. reg. address onwards and stores them in buff
    oax = (((buff[1] << 8) | buff[0])>>2); // [LSB]
    oay = (((buff[3] << 8) | buff[2])>>2); // [LSB]
    oaz = (((buff[5] << 8) | buff[4])>>2); // [LSB]
    //oaz = oaz-ACC_1G;                      // [LSB] 1010 LSB = 1G
    oaz = oaz-1010;
    sum_oax += oax;
    sum_oay += oay;
    sum_oaz += oaz;

}
  off_ax = sum_oax/200;
  off_ay = sum_oay/200;
  off_az = -sum_oaz/200;

}  // End of setup*
* *
*void loop()
{

getAccData();

currentTime = micros();                // [us]
  cycleTime = currentTime - previousTime; // [us]
  previousTime = currentTime;            // [us]

angleX = angle_xa + (1-a)(angle_gx + error_x0.9f); // 2% accelerometer, 98% gyro 
  angleY = angle_y
a + (1-a)(angleY + angle_gy); // Pitch range: -90º to 90º
  angleZ = heading
a + (1-a)*(angleZ + angle_gz); // Pitch range: 0º to 360º

}*
```
Mind that I have ommited everything relative to the gyro so as to make it more understandable.
The actual code is like 800 lines long. LOL