chipKIT® Development Platform

Inspired by Arduino™

Wi-Fire I2C problems with external sensor

Created Fri, 26 Jun 2015 12:03:09 +0000 by Demian


Demian

Fri, 26 Jun 2015 12:03:09 +0000

Hi,

I've connected my ChipKit Wi-Fire board to a temperature/barometer sensor (BMP085) via I2C to read out temperature and pressure, and then calculate the altitude. I found some code online which does this for the Arduino Uno, and it works out of the box. But for the Wi-Fire I only get nonsensical numbers out.

The code is as follows:

#include <Wire.h>

#define BMP085_ADDRESS 0x77  // I2C address of BMP085

const unsigned char OSS = 0;  // Oversampling Setting

// Calibration values
int ac1;
int ac2; 
int ac3; 
unsigned int ac4;
unsigned int ac5;
unsigned int ac6;
int b1; 
int b2;
int mb;
int mc;
int md;

int time_start;
int time_end;

// b5 is calculated in bmp085GetTemperature(...), this variable is also used in bmp085GetPressure(...)
// so ...Temperature(...) must be called before ...Pressure(...).
long b5; 

short temperature;
long pressure;

// Use these for altitude conversions
const float p0 = 101325;     // Pressure at sea level (Pa)
float altitude;

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  bmp085Calibration();
}

void loop()
{
  time_start = micros();
  temperature = bmp085GetTemperature(bmp085ReadUT());
  pressure = bmp085GetPressure(bmp085ReadUP());
  altitude = (float)44330 * (1 - pow(((float) pressure/p0), 0.190295));
  time_end = micros();
  
  
  Serial.print("Temperature: ");
  Serial.print(temperature, DEC);
  Serial.println(" *0.1 deg C");
  Serial.print("Pressure: ");
  Serial.print(pressure, DEC);
  Serial.println(" Pa");
  Serial.print("Altitude: ");
  Serial.print(altitude, 2);
  Serial.println(" m");
  
  Serial.print("Time: ");
  Serial.print(time_end-time_start, DEC);
  Serial.println(" us");
  
  Serial.println();
  
  delay(1000);
}

// Stores all of the bmp085's calibration values into global variables
// Calibration values are required to calculate temp and pressure
// This function should be called at the beginning of the program
void bmp085Calibration()
{
  ac1 = bmp085ReadInt(0xAA);
  ac2 = bmp085ReadInt(0xAC);
  ac3 = bmp085ReadInt(0xAE);
  ac4 = bmp085ReadInt(0xB0);
  ac5 = bmp085ReadInt(0xB2);
  ac6 = bmp085ReadInt(0xB4);
  b1 = bmp085ReadInt(0xB6);
  b2 = bmp085ReadInt(0xB8);
  mb = bmp085ReadInt(0xBA);
  mc = bmp085ReadInt(0xBC);
  md = bmp085ReadInt(0xBE);
}

// Calculate temperature given ut.
// Value returned will be in units of 0.1 deg C
short bmp085GetTemperature(unsigned int ut)
{
  long x1, x2;
  
  x1 = (((long)ut - (long)ac6)*(long)ac5) >> 15;
  x2 = ((long)mc << 11)/(x1 + md);
  b5 = x1 + x2;

  return ((b5 + 8)>>4);  
}

// Calculate pressure given up
// calibration values must be known
// b5 is also required so bmp085GetTemperature(...) must be called first.
// Value returned will be pressure in units of Pa.
long bmp085GetPressure(unsigned long up)
{
  long x1, x2, x3, b3, b6, p;
  unsigned long b4, b7;
  
  b6 = b5 - 4000;
  // Calculate B3
  x1 = (b2 * (b6 * b6)>>12)>>11;
  x2 = (ac2 * b6)>>11;
  x3 = x1 + x2;
  b3 = (((((long)ac1)*4 + x3)<<OSS) + 2)>>2;
  
  // Calculate B4
  x1 = (ac3 * b6)>>13;
  x2 = (b1 * ((b6 * b6)>>12))>>16;
  x3 = ((x1 + x2) + 2)>>2;
  b4 = (ac4 * (unsigned long)(x3 + 32768))>>15;
  
  b7 = ((unsigned long)(up - b3) * (50000>>OSS));
  if (b7 < 0x80000000)
    p = (b7<<1)/b4;
  else
    p = (b7/b4)<<1;
    
  x1 = (p>>8) * (p>>8);
  x1 = (x1 * 3038)>>16;
  x2 = (-7357 * p)>>16;
  p += (x1 + x2 + 3791)>>4;
  
  return p;
}

// Read 1 byte from the BMP085 at 'address'
char bmp085Read(unsigned char address)
{
  unsigned char data;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 1);
  while(!Wire.available())
    ;
    
  return Wire.read();
}

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  
  return (int) msb<<8 | lsb;
}

// Read the uncompensated temperature value
unsigned int bmp085ReadUT()
{
  unsigned int ut;
  
  // Write 0x2E into Register 0xF4
  // This requests a temperature reading
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x2E);
  Wire.endTransmission();
  
  // Wait at least 4.5ms
  delay(5);
  
  // Read two bytes from registers 0xF6 and 0xF7
  ut = bmp085ReadInt(0xF6);
  return ut;
}

// Read the uncompensated pressure value
unsigned long bmp085ReadUP()
{
  unsigned char msb, lsb, xlsb;
  unsigned long up = 0;
  
  // Write 0x34+(OSS<<6) into register 0xF4
  // Request a pressure reading w/ oversampling setting
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF4);
  Wire.write(0x34 + (OSS<<6));
  Wire.endTransmission();
  
  // Wait for conversion, delay time dependent on OSS
  delay(2 + (3<<OSS));
  
  // Read register 0xF6 (MSB), 0xF7 (LSB), and 0xF8 (XLSB)
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.write(0xF6);
  Wire.endTransmission();
  Wire.requestFrom(BMP085_ADDRESS, 3);
  
  // Wait for data to become available
  while(Wire.available() < 3)
    ;
  msb = Wire.read();
  lsb = Wire.read();
  xlsb = Wire.read();
  
  up = (((unsigned long) msb << 16) | ((unsigned long) lsb << 8) | (unsigned long) xlsb) >> (8-OSS);
  
  return up;
}

The output from the Arduino Uno is as follows, and seems correct:

Temperature: 234 *0.1 deg C Pressure: 100024 Pa Altitude: 108.88 m Time: 12412 us

The output from the Wi-Fire board, on the other hand, is

Temperature: 1190 *0.1 deg C Pressure: 3328 Pa Altitude: 21188.65 m Time: 10692 us

This is obviously not correct. I've changed things like "Wire.read" with "Wire.receive" and so forth, but still get the same problem. I would assume that since the Arduino Uno is 8 bit, and the Wi-Fire is 32 bit, there shouldn't be a problem when declaring the variables and doing the mathematical operations that are being done. Any ideas?


majenko

Fri, 26 Jun 2015 12:06:02 +0000

Do you have pullup resistors (~3.3KΩ) attached to the SCL and SDA lines for I2C? The Arduino (wrongly) enables the internal pullup resistors on those ports and most people who have come from the Arduino realm don't know that you actually need pullup resistors on those lines.

It's not their fault, it's Arduino's fault for teaching them the wrong things.


Demian

Fri, 26 Jun 2015 12:09:08 +0000

Yes, I do. I'm using 1 kOhm resistors though, but that shouldn't make any difference I think. Or is the I2C channel that sensitive?


majenko

Fri, 26 Jun 2015 13:48:37 +0000

No, 1KΩ is fine. It's all a balancing act between current consumption and slew rate. 3.3KΩ is usually reckoned to be a good middle value to use. 1KΩ will just consume a little more current, but actually allow a more reliable connection over longer distances.

Ok, so the next step is to confirm that you are actually getting the right readings in from the sensor. Can you print the raw values returned by the Wire.read() functions where applicable and compare them to the same thing on the Uno?


Demian

Tue, 30 Jun 2015 11:48:25 +0000

Hi,

Sorry about the late reply. Ok, so I started debugging, doing what you said, and I noticed a difference already in the readouts of the calibration coefficients. These are the results:

ARDUINO:

ac1: 6935 ac2: -1265 ac3: -14475 ac4: 33716 ac5: 33716 ac6: 22489 b1: 5498 b2: 69 mb: -32768 mc: -11075 md: 2432

CHIPKIT:

ac1: 6935 ac2: 64271 ac3: 51061 ac4: 33716 ac5: 33716 ac6: 22489 b1: 5498 b2: 69 mb: 32768 mc: 54461 md: 2432

Obtaining the calibration coefficients is done in the bmp085ReadInt routine, which I'll post again for ease of finding it. So for instance, to get coefficient ac1 you would call ac1 = bmp085ReadInt(0xAA); All the coefficients are defined as "int", except ac4, ac5, ac6, which are "unsigned int". Any idea what's going wrong?

// Read 2 bytes from the BMP085
// First byte will be from 'address'
// Second byte will be from 'address'+1
int bmp085ReadInt(unsigned char address)
{
  unsigned char msb, lsb;
  
  Wire.beginTransmission(BMP085_ADDRESS);
  Wire.send(address);
  Wire.endTransmission();
  
  Wire.requestFrom(BMP085_ADDRESS, 2);
  while(Wire.available()<2)
    ;
  msb = Wire.receive();
  lsb = Wire.receive();
  
  return (int) msb<<8 | lsb;
}

majenko

Tue, 30 Jun 2015 12:01:43 +0000

Sounds like integer size problems to me. On the Arduino, because it's only an 8-bit MCU, the "int" is only 16 bits wide. On the chipKIT it's 32-bits wide. So, when you're building a signed integer up from two bytes you don't get the sign bit represented correctly.

Instead of working with "int" you should work with size-specific variables. Where the arduino uses "int", use "int16_t". Where it uses "unsigned int" use "uint16_t"


Demian

Tue, 30 Jun 2015 12:10:49 +0000

Excellent! Now it works. Thanks for your help!