Using Wire Library to connect to MLX90620 over I2C

Hello,

I read through 4-5 threads where people were connecting MLX90620 thermopile arrays to their arduino's. In one thread there was some sample code that used an external I2C library called I2CMaster. I'm very new to Arduino and had originally attempted to code this using the built-in Wire library. However my code doesn't seem to work and I think it might be because the Wire library doesn't support the type of communication I need.

The datasheet for the MLX90620 shows that the I2C communications for a read should go like this:

^SLAVEWRT-OPCODE-STARTADR-STEPADR-NUM-^SLAVERD-BYTE-BYTE-...-STOP

Where the '^' are START and the '-' are ACK. The code I'm using is below:

#include <Wire.h>

#define ADDRESS 0x60

void setup()
{
  Serial.begin(9600);
  Wire.begin();
  delay(15);           // Ensure initialization is complete
}

void loop()
{
  Serial.println("--Begin--");
  thermo_read(0x00,0x01,0x40); //Start at 0th sensor, step by 1, read 40 sensors
  Serial.println("--End--");
  delay(1000);
}

void thermo_read(byte start, byte adrStep, byte num){
  Wire.beginTransmission(ADDRESS);
  Serial.print(".");
  Wire.write(0x02); // Read IR sensor OpCode
  Serial.print(".");
  Wire.write(start);
  Serial.print(".");
  Wire.write(adrStep);
  Serial.print(".");
  Wire.write(num);
  Serial.print(".");
  Wire.endTransmission();
  Serial.print(".");
  Wire.requestFrom(ADDRESS,(int)num); //might be 2*num, see datasheet pg 28
  Serial.print(".");
  while(!Wire.available()){ delay(1);} // Wait for response
  Serial.print("!");
  while(Wire.available()){
    Serial.print(Wire.read());
    Serial.print("-");
  }
  Serial.println();
}

When I run the above, it runs as far as the last '.' but never prints the '!', indicating it is waiting on the Wire.available() call. Now I am suspecting that the Wire.requestFrom() call is not sending the second START signal. I think this because the I2CMaster code had a call to i2c_rep_start() which sounds like the repeated start signal in the middle of the sequence. Am I correct in assuming that? Or is the problem that the frequency is incorrect (I don't know if I2C negotiates speed, I doubt it does)

Any help would be appreciated.
~Vitus

First, you don't have to do this:

  Wire.requestFrom(ADDRESS,(int)num); //might be 2*num, see datasheet pg 28
  Serial.print(".");
  while(!Wire.available()){ delay(1);} // Wait for response

Wire.requestFrom does not return until it has the data, so you don't need to "wait for response".

Wire.requestFrom returns the number of bytes obtained. You should check that is what you expect.


  thermo_read(0x00,0x01,0x40); //Start at 0th sensor, step by 1, read 40 sensors

...

void thermo_read(byte start, byte adrStep, byte num){

...

  Wire.requestFrom(ADDRESS,(int)num); //might be 2*num, see datasheet pg 28

You are requesting 64 bytes of data (0x40). The Wire buffer is 32 bytes. It can never return 64 bytes.


BTW 0x40 is not "40 sensors". It is 64. 0x40 is hex.

Make it easy for yourself, use decimal:

  thermo_read (0, 1, 40); //Start at 0th sensor, step by 1, read 40 sensors

Even then, you can't get 40 bytes back. That is still more than 32.

Start with the I2C scanner, Arduino Playground - I2cScanner

The Wire library has been updated, the Wire.requestFrom has now a third flag for stop condition.

That solved a lot of problems with strange I2C chips, but I don't know if that is any good for the MLX90620.

With regards to Nick Gammon,

Wire.requestFrom returns the number of bytes obtained.

I am following what is documented at Wire - Arduino Reference which indicates that Wire.requestFrom() does not have a return value.

BTW 0x40 is not "40 sensors". It is 64. 0x40 is hex.

There are 64 pixels in this device, 4 rows of 16. So 0x40 is actually what I want. I'm well aware of Hex, thanks. I slipped in the comments. A cursory google for the MLX90620 indicates it is a 64 element array.

The Wire buffer is 32 bytes. It can never return 64 bytes.

I'll overlook that you used the word 'return' when you clearly meant 'buffered'. Since the Wire library only buffers 32 bytes I guess I am forced to make two calls to Wire.requestFrom(), each for half of my original request. (in fact, I believe each pixel actually is recorded as two bytes so I'll have to make 4 calls).

I will look into the link you left. I quickly scanned it and I see it has a shapshot from a logic analyzer. This will be useful as I managed to capture the conversation on an oscilloscope while I was at the lab. In the future, try to be a bit less hostile towards people who are new with respect to a technology (and don't assume they know nothing about programming). The 32 byte buffer is not documented on the Wire.requestFrom() documentation so anyone new to the library would have made that mistake.

With regards to Erdin,

I'm wondering if the 'restart' mentioned in the Wire.requestFrom() description is identical to the second 'start' signal specified in the datasheet for the MLX90620 on page 28 where the read conversation is illustrated. If so then I believe I should pass false as the optional parameter. I will run the Scanner program that you linked to later today to ensure my circuit setup is correct and then I will proceed to test my program, with alterations to adjust the call to Wire.requestFrom(false) and possibly another to remove the while(!Wire.available()) loop, despite it not really hurting if left in.

MLX90620 datasheet: Error - Melexis

Vitus:
I am following what is documented at Wire - Arduino Reference which indicates that Wire.requestFrom() does not have a return value.

Thank you for pointing that out. I have raised an error report over this:

The Wire buffer is 32 bytes. It can never return 64 bytes.

I'll overlook that you used the word 'return' when you clearly meant 'buffered'.

Put it this way, Wire.requestFrom will never return 64.

Since the Wire library only buffers 32 bytes I guess I am forced to make two calls to Wire.requestFrom(), each for half of my original request. (in fact, I believe each pixel actually is recorded as two bytes so I'll have to make 4 calls).

That's probably wise, although you can edit two files and increase the buffer size. Be warned that whatever size you choose, it makes 5 of them. So a buffer of 32 already uses 160 bytes of your RAM.

The 32 byte buffer is not documented on the Wire.requestFrom() documentation so anyone new to the library would have made that mistake.

I wasn't trying to accuse you. I was pointing out things I found out for myself the hard way.

The limitation also applies to sending (beginTransmission ... endTransmission) as well. The documentation is incomplete in that respect. That applies to other things, like the size of the serial buffer.

My I2C page ( Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino ) tries to cover a few of the "gotchas" in the use of the wire library.

Vitus:
... and possibly another to remove the while(!Wire.available()) loop, despite it not really hurting if left in.

It can hurt because, first it does nothing useful, and second, if nothing (zero) is returned by Wire.requestFrom (bus arbitration issue, device not on or something) then that loop will make your code hang forever, which presumably you don't want.

The correct test is to check the return value from Wire.requestFrom, and not to test for Wire.available.

I was looking through the datasheet for the MLX90620 and if I read it correctly it wants the I2C bus to run at 1Mhz. Now I'm not certain if that has to match the arduino exactly or not (I imagine it should). The arduino cannot run the I2C that fast, as if you do the TWBR calculation you get 0. Now the wikipedia for I2C says that devices can hold the clock line low as needed. With that in mind, will the arduino hold the clock and run it at 400 Mhz? Or will I have to find a way to change the MLX90620's preferred frequency.

Yesterday I built a level shifter using a CD4007UBE transistor package and some 4004 diodes. I hooked up the arduino and the thermopile array and I was not getting any useful results with the I2C Scanner. I varied the pull-up resistor values but I realized today that we did use low enough values (10K was what we had), so I'll go back today and try 2.2K.

On my page ( Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino ) I have some sample TWBR values (see "Sample TWBR values", lol).

400 KHz is the maximum I2C frequency on the Uno, you won't get 1 MHz. However since I2C is self-clocking I doubt that will matter too much.

Ok, I built and verified my level shifter using 10K pull up resistors. I followed the schematic exactly, except I left out the pull up resistors on the Arduino side because they are internal (If I left them in the voltage on SDA and SCL dropped to 1.5ish volts).

The results of running the I2C scanner example are that all odd numbered addresses from 1 to 0x77 are found to have devices on them. That seems odd, but I can't imagine why it would be so. Sadly I don't have any other I2C devices with which to test this besides the MLX90620.

Schematic: http://tinyurl.com/BiLvlShftr
Rp = 10K (removed on the 5V side)
Vdd1=2.5V (Aglient DC Power Supply)
Vdd2=5.0V (Aglient DC Power Supply)
The Arduino's GND is attached to the COM and GND on the Aglient DC Power Supply.

The address should be shifted right one, so although the datasheet says 0x60 for the IR data and 0x50 for the EEPROM, in your code you want 0x30 for the IR and 0x28 for the EEPROM.

The master is addressing the slave device by sending an 7-bit slave address after the START condition. The first seven bits are dedicated for the address and the 8th is Read/Write (R/W) bit. This bit indicates the direction of the transfer:
Read (HIGH) means that the master will read the data from the slave
Write (LOW) means that the master will send data to the slave Mlx90620 is responding to 2 different slave addresses:

Page 26 of the datasheet.

OK, I guess I had changed the I2C Scanner code in some way (or my partner did when I wasn't looking). I recopied it from the site and the following address are occupied. So, on to step 2 - my own code. I will report back with results.

I2C Scanner
Scanning...
I2C device found at address 0x50  !
I2C device found at address 0x51  !
I2C device found at address 0x52  !
I2C device found at address 0x53  !
I2C device found at address 0x54  !
I2C device found at address 0x55  !
I2C device found at address 0x56  !
I2C device found at address 0x57  !
I2C device found at address 0x60  !
done

Evidently you aren't using my scanner here: http://www.gammon.com.au/forum/?id=10896&reply=6#reply6

That reports the address you need to plug into Wire.begin, not it doubled.

I don't see any functional difference between the scanner you linked to and the one at Arduino Playground - I2cScanner. If/when I transition to a different library (as this topic's title specifically mentions using the Wire library) I will keep the 7 vs. 8-bit addressing in mind. I understand there are some issues with the Wire library hanging. If it is unable to formulate messages in the manner I need or I experience hangs I transition to I2CMaster or another I2C library. Until then, I'd like to keep it simple and work with the built-in libraries , just to get my feet wet.

OK, So reporting back with results.

It looks like the Wire library will in no way work for communicating with this device because of the repeated start signal that Wire can't perform. I switched to using the I2C library located in the link below. I'm using revision 5 of that library. I have achieved the reading of raw values from the sensors, so now I just need to convert those into real temperatures. That will be a topic for a different thread. The final code I used is below.

#include <I2C.h>

#define ADDRESS 0x50

int   IRDATA[64];



void setup()
{
  Serial.begin(9600);
  I2c.begin();
  //Serial.println("Scanning...");
  //I2c.scan();                          // Uncomment to scan for addresses (and check 7 vs. 8-bit scheme)
  Serial.println("Polling...");
  delay(15);
}

void loop()
{
  thermo_read(0x00,0x01,0x40);
  for(int i=0; i<63; i++) {
    if(!(i % 16)) Serial.println();
    Serial.print(IRDATA[i]);
    Serial.print("|");
  }
  Serial.println(IRDATA[64]);
  Serial.println();
  delay(1000);
}

void thermo_read(byte start, byte adrStep, byte num){
  byte High, Low;
  char cmd[3];
  cmd[0] = (char)start;  // Starting address (0-63)
  cmd[1] = (char)adrStep;// Address Stepping (<=64)
  cmd[2] = (char)num;    // Number of addresses
  I2c.write(ADDRESS,0x02,cmd);
  I2c.read(ADDRESS,num*2,(byte*)IRDATA); // LSB:MSB
  byte temp;
  for(int i=0; i<(num*2); i+=2) {
    temp                 = ((byte*)IRDATA)[i];
    ((byte*)IRDATA)[i]   = ((byte*)IRDATA)[i+1];
    ((byte*)IRDATA)[i+1] = temp;
  }
}

Website: DssCircuits.com is for sale | HugeDomains
Direct Link to Archive: DssCircuits.com is for sale | HugeDomains