I2C with MPL3115A2 Altitude/Pressure Sensor

Hey all. I'm trying to do something really simple and I'm having a bit of trouble getting it to work. I'm working with the MPL3115A2 Altitude/Pressure Sensor and a pic32 uC32 board, and I'm trying to communicate between the two using I2C. (uC32 board is similar enough to arduino that it's practically the same in terms of coding).

I'm using the wire library and I'm simply trying to read register 0x0C from the MPL3115A2, which should give me the device ID.

Here's a code snippet (the define is at the top of the code and the rest is in the main loop):

#define barAddress 0x60

Wire.beginTransmission(barAddress);
Wire.send(0x0C);
Wire.endTransmission();

Wire.requestFrom(barAddress, 1);
uint8_t a = Wire.receive();
Serial.println(a, HEX);

So I start the transmission with address 0x60 (From the datasheet: The standard 7-bit I2C slave address is 0x60 or 1100000. 8-bit read is 0xC1, 8-bit write is 0xC0.). Then I send 0x0C because that's the register I want to access. I then end transmission, and request 1 byte from address 0x60, receive that bit into a 8-bit variable, then print it.

The problem I run into is that when I print it, I just get 0. I don't get the device ID, just 0. No matter what register I try to read, I get 0.

I've been banging my head against a wall for the past few days trying to get this to work. I've attached something I've captured with a logic analyzer, as well as a list of registers from the datasheet of the MPL3115A2 that I've been trying to access.

Any help, comments, or questions would be greatly appreciated : )

logic.PNG

I think the problem may be with the Wire.send(0x0C) portion of the code.

It seems like when I do that, the sensor doesn't know that I want to access the 0x0C register for some reason. It seems like I'm just sending it the number '12' and it's eating that number and not doing anything with it really..

It might be that the I2C read transaction needs to use a repeated start between the Write address and the Read data/

Wire.begin(); // only needed once (setup())

Wire.beginTransmission(address);
Wire.write(0x0c);
Wire.endTransmission(false); //the little false here tells Wire to not release the buss ('owning it')
// possibly the device is resetting when a full stop is sent and the internal device address register is
// being reset back to 0 instead of 0x0c

Wire.requestFrom(address,1);
value = Wire.read();

Chuck.

Hey Chuck. Thank you so much for the reply.

I'm unable to add that false parameter that you suggested. The only endTransmission() method I have doesn't have any arguments.

I have some extra information however.
I tried doing exactly what I'm doing now but with a different sensor: the MAG3110 Three-Axis Digital Magnetometer. When I do the exact things I was doing before but replace barAddress (0x60) with magAddress (0x0E), I can get all the information I want from whatever registers I want to access and/or change.

So the code I'm using works with the magnetometer but not the barometer.

Could it be that the barometer register gets reset back to 0 when doing endTransmission() but the magnetometer doesn't? Seems odd.

What version of the arduino sw are you using?

Says that since version 1.01 Wire.endTransmission() accepts a boolean what controls Stop generation.

I know for a fact that Freescale's Accelerometers MMA845x series, require the repeated Start method for communications. Microchip's 24LCxxx EEProms will work either way.

Now something else, what about voltage. I have to use level shifters when I communicate with my Accel's they run off 3.3v but my Mega2560's are 5v.

Does your device have a config option to 'auto' increment the register pointer?
If it does you could try to do a Wire.requestFrom(0x60,30); and look at each byte returned.
like:
Wire.requestFrom(0x60,30);
while(Wire.available()){
Serial.print(Wire.read(),DEC);
Serial.print(" ");
}
Serial.println();

I have a lib that I use for I2C, it might work for you

Chuck.

check out my Arduino Mega2560 kickstarter project: Memory Panes
It adds 1,024KB of RAM to a MEGA2560.

Just found the answer in the spec sheet MPL3115A2
On page 16 Second paragraph:

A low to high transition on the SDA line while the SCL line is high is defined as a stop condition (STOP). A data transfer is always terminated by a STOP. A master may also issue a repeated START during a data transfer. Device expects repeated STARTs to be used to randomly read from specific registers.
The standard 7-bit I2C slave address is 0x60 or 1100000. 8-bit read is 0xC1, 8-bit write is 0xC0.

Specifically This sentance

Device expects repeated STARTs to be used to randomly read from specific registers.

You will have to find how to implement the repeated start mode for your PIC code.

Chuck.

check out my Arduino Mega2560 kickstarter project: Memory Panes
It adds 1,024KB of RAM to a MEGA2560.

So the three sensors I'm using (MAG3110 magnetometer, MMA8491Q accelerometer, and MPL3115A2 pressure sensor) all have similar sentences in their data sheet saying:

...Then the master (or MCU) transmits the address of the register to
read and the MAG3110 sends an acknowledgement. The master (or MCU) transmits a repeated start condition (SR), followed by
the slave address with the R/W bit set to “1” for a read from the previously selected register....

...The MMA8491Q expects repeated STARTs to be used to
randomly read from specific registers...

...Device (MPL311A52) expects repeated
STARTs to be used to randomly read from specific registers...

This all makes me wonder why I can access registers randomly for the magnetometer and accelerometer but not the pressure sensor.

So, despite being super slow, I could probably read multiple bytes from the pressure sensor starting at the first register, and use the auto-increment to get to the register I need the data from.

EDIT: The auto-increment idea I said above didn't work :frowning:

When you say the auto inc didn't work, did you mean all you received were zero's?

Chuck.

Correct.

In the meantime, I've written my own code for the barometer in which I've simply hooked up the SDA and SCL pins to a couple of random digital pins, and then bit-banged to get the registers I want using a repeated start. And it works! ... mostly.

My code for this is pretty ugly, but here it is:

#define RG03_sda 2
#define RG02_scl 1

uint8_t r = 0;

void setup() {
//  Serial.begin(9600);
  
  pinMode(RG03_sda, OUTPUT);
  pinMode(RG02_scl, OUTPUT);
}

void loop() {
  r = barometer();
//  Serial.println(r, HEX);
  delay(10);
}

uint8_t barometer() {
  r = 0;
  
  //Start condition
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG03_sda, HIGH);
  delay(1);
  digitalWrite(RG03_sda, LOW);
  delay(1);
  digitalWrite(RG02_scl, LOW);
  
  //Pick slave device to write to
  int i = 0;
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((0xC0 >> (7-i)) & 0x01)); //0xC0 is write, 0xC1 is read
    digitalWrite(RG02_scl, HIGH);
    delay(1);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  delay(1);
  digitalWrite(RG02_scl, LOW);
  
  //Choose register
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((0x0C >> (7-i)) & 0x01)); //0x0C is device identification register
    digitalWrite(RG02_scl, HIGH);
    delay(1);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  delay(1);
  digitalWrite(RG02_scl, LOW);
  
  //repeat start
  digitalWrite(RG03_sda, HIGH);
  digitalWrite(RG02_scl, HIGH);
  delay(1);
  digitalWrite(RG03_sda, LOW);
  digitalWrite(RG02_scl, LOW);
  
  //Pick slave device to read from
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((0xC1 >> (7-i)) & 0x01)); //0xC0 is write, 0xC1 is read
    digitalWrite(RG02_scl, HIGH);
    delay(1);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  delay(1);
  digitalWrite(RG02_scl, LOW);
  
  //switch pinmode so I can read from sda pin rather than send
  pinMode(RG03_sda, INPUT);
  
  //Read the data in the device identification register
  for(i = 0; i < 8; i++) {
    r |= (digitalRead(RG03_sda) << (7-i));
    digitalWrite(RG02_scl, HIGH);
    delay(1);
    digitalWrite(RG02_scl, LOW);
  }
  
  //switch pinmode back to output on sda pin
  pinMode(RG03_sda, OUTPUT);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  delay(1);
  digitalWrite(RG02_scl, LOW);
  
  //stop
  digitalWrite(RG03_sda, LOW);
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG03_sda, HIGH);
  
  return r;
}

The problem I run into is that when I begin the serial port, my SCL line doesn't work any longer and it stays HIGH no matter what. I know I'm getting the correct data since I can see exactly what's going on through the logic analyzer (pic attached). The device register should hold 0xC4 and that's exactly what I'm reading so that's good. But when I try to display that over the serial port it screws everything up..

Thoughts?

Edit: I should clarify that the code works as is with the Serial lines commented out. But as soon as I uncomment the "Serial.begin(9600)" line, the SCL line stays high no matter what and I get really weird stuff on the SDA line. The pic from the logic analyzer is with the Serial lines commented out so it works without the Serial stuff. :confused:

EDIT 2: Just found out that when using serial communication you can't use pins 0 and 1. Switched the pins to 2 and 3 and it works now! Thanks so much for your help in general chuck. I'll probably just ditch the wire library and do the bit-banging myself for all of the sensors.