Cant read multiple MCP9808 sensors

I want to read three MCP9808 sensor breakout boards from adafruit. You can set the address on these boards with jumpers as described in adafruits tutorial. The default address is 0x18, while other available addresses are 0x19, 0x1A, and 0x1C. https://learn.adafruit.com/adafruit-mcp9808-precision-i2c-temperature-sensor-guide/pinouts

there is a nice library by romainmp for particle devices here.


also available on particle build

There is an example for reading a single sensor - works great.

However, when I try to add a second sensor at 0x19 - the callout to the second sensor just keeps reading the first sensor at the default address of 0x18. Double checked all my wiring. Here is my code. Perhaps I am not addressing the second sensor properly.

Here is my code. Note, all I did was add a second instance of the sensor to romainmp’s example.

#include "MCP9808.h"

MCP9808 mcp = MCP9808();
MCP9808 mcp1 = MCP9808(0x19);

void setup()
{
	Serial.begin(9600);

    delay(5000);
    
	Serial.println("MCP9808 test");

	// Wait for the sensor to come up
	while(! mcp.begin()){
	    Serial.println("MCP9808 -0 not found");
	    delay(500);
	}
// Wait for the sensor to come up
	while(! mcp1.begin()){
	    Serial.println("MCP9808 -1 not found");
	    delay(500);
	}

	// Set resolution to the maximum (slowest conversion)
	mcp.setResolution(MCP9808_SLOWEST);
	mcp1.setResolution(MCP9808_SLOWEST);

	Serial.println("MCP9808 OK");
}

void loop()
{
	Serial.print("Temp0:"); Serial.println(mcp.getTemperature(), 4);
	delay(250);
	Serial.print("Temp1:"); Serial.println(mcp1.getTemperature(), 4);
	delay(250);
}

EDIT: new will only return a pointer which isn't helpful for this issue. It doesn't even compile.

error: invalid conversion from 'MCP9808*' to 'uint8_t {aka unsigned char}' [-fpermissive]
 MCP9808 mcp = new MCP9808();

Try adding with new. The above may actually be using the same object which is why you are getting the same reading.

MCP9808 mcp = new MCP9808();
MCP9808 mcp1 = new MCP9808(0x19);

Here is a stackoverflow post that might help explain this situation a little bit.

There seems to be two problems here, and while I’ve solved the first, I can’t figure out the second. The first problem is an error in the library’s constructor for the MCP9808 object,

_i2cAddr = (MCP9808_DEFAULT_ADDRESS & addr == MCP9808_DEFAULT_ADDRESS) ? addr : MCP9808_DEFAULT_ADDRESS;

The equality operator, ==, has a higher precedence than the bitwise AND operator, &, so the above line is doing a bitwise AND on the result of addr == MCP9808_DEFAULT_ADDRESS (which will be 1 or 0) and 0x18. This will always be 0 (false). So, all devices will have the same 0x18 address. This can be fixed by adding parentheses,

_i2cAddr = ((MCP9808_DEFAULT_ADDRESS & addr) == MCP9808_DEFAULT_ADDRESS) ? addr : MCP9808_DEFAULT_ADDRESS;

This fix does give the devices the correct address, but I’ve only gotten it to work one time (using 0x1C and tying A2 to power). I’m not sure what’s going on with that, it only worked that one time, and all other attempts have given me the “MCP9808 -1 not found” message.

1 Like

Thanks @Ric,

Your explanation on the _i2CAddr = error makes sense and is consistent with what we were seeing. I’ll try your fix on our end and report back. Sounds like there is still an issue - I’ll check @cermak suggestions as well. Thanks for the help.

I don’t think new is the way to go in this context. The new operator would give you a MCP9808*.

And the stackoverflow reference does suggest that the “non-new” instance would be created od the stack, which would only be true for automatic objects, but in this case we are creating a global object.

2 Likes

@ScruffR- I’ll edit that post. That is wrong advice. The new argument will return a pointer. @bko most likely found one main problem with the assignment. I eyeballed it and thought it was ok. I was going to suggest sticking some Serial.print statements in there to be sure the address is correct after the assignment.

This problem is curious.

The compiler does issue a warning for that assignment which should and did send up a red flag.

/Users/cermak/Documents/src/classtest/src/MCP9808.cpp: In constructor 'MCP9808::MCP9808(uint8_t)':
/Users/cermak/Documents/src/classtest/src/MCP9808.cpp:6:45: warning: suggest parentheses around comparison in operand of '&' [-Wparentheses]
  _i2cAddr = (MCP9808_DEFAULT_ADDRESS & addr == MCP9808_DEFAULT_ADDRESS) ? addr : MCP9808_DEFAULT_ADDRESS;

The behavior is confirmed adding a Serial.println() and adding a routine to access the stored private value.

MCP9808 initialized
mcp:0x18
mcp1:0x18

Compact arguments are great. Lets temporarily unwind that routine. All we really want is if there is a passed address, use it, otherwise use the default.

One way to solve this is to create two constructor functions: (1) with no argument and (2) with the argument. This is more in line with the patterns I’ve seen so far in c++.

MCP9808.h

public:
    // Constructor
    MCP9808(); // use the default address
    MCP9808(uint8_t addr); // supply a different address

MCP9808.cpp

MCP9808::MCP9808(){
        _i2cAddr = MCP9808_DEFAULT_ADDRESS;
}

MCP9808::MCP9808(uint8_t addr){
	// Address can be changed from 0x18 to 0x1F, default is 0x18
	// Only the least significant bits can be modified with pins A0 to A2
	//_i2cAddr = (MCP9808_DEFAULT_ADDRESS & addr == MCP9808_DEFAULT_ADDRESS) ? addr : MCP9808_DEFAULT_ADDRESS;
        _i2cAddr = addr;
}

This now assigns the address properly to the separate objects.

MCP9808 initialized
mcp:0x18
mcp1:0x19

Now looking at the header, the author actually has a default argument on the assignment that I didn’t see before.

	// Constructor
	MCP9808(uint8_t addr = MCP9808_DEFAULT_ADDRESS);

We can revert back to the original code with one function and simply use:

_i2cAddr = addr;

MCP9808.h

class MCP9808
{
public:
	// Constructor
	MCP9808(uint8_t addr = MCP9808_DEFAULT_ADDRESS);

MCP9808.cpp

#include "MCP9808.h"

MCP9808::MCP9808(uint8_t addr){
        // Address can be changed from 0x18 to 0x1F, default is 0x18
        // Only the least significant bits can be modified with pins A0 to A2
        //_i2cAddr = (MCP9808_DEFAULT_ADDRESS & addr == MCP9808_DEFAULT_ADDRESS) ? addr : MCP9808_DEFAULT_ADDRESS;
        _i2cAddr = addr;
}

This also yields now proper address assignments.

MCP9808 initialized
mcp:0x18
mcp1:0x19

The question is, do you see different readings. The rest of the code looks ok. The library checks to be sure it does not initialize Wire twice.

The simple change I mentioned above, fixes the address assignment, so I don’t think that’s the problem at this point. I’ve tested with just one MCP9808, and I can’t get it to work reliably with any address other than the default one. I can’t see anything in the library that should cause this, so maybe something wrong with the breakout board?

This is why I usually suggest this way to instantiate an object

// instead of
//MCP9808 mcp = MCP9808();
//MCP9808 mcp1 = MCP9808(0x19);
// do this
MCP9808 mcp;        // instantiate via the default constructor using the default address
MCP9808 mcp1(0x19); // instantiate via the dedicated address constructor

The original version is less efficient and prone to errors like the above.
In both cases the actual object will be created via the default constructor - using the default address - first, then a temporary object is created (once default and once dedicated address) and finally the temporary object gets “assigned” to the orriginal object via the assignment/copy operator (if not explicitly implemented a default copy operator is used).
But if the assignment/copy operator does not copy the private _i2cAddr field - which seems to happen here by the default op) - it’s not a proper copy.

While it is true that the library should be corrected that way, the above suggestion should be able to solve the issue without the need to “mess” with the library.

1 Like

Thanks @cermak,

I tried your fix with two sensors and it worked fine!

I had actually implemented the same change myself and just hard coded all the addresses for each object - not realizing the default was address was set over the header file as you pointed out

class MCP9808
{
public:
// Constructor
MCP9808(uint8_t addr = MCP9808_DEFAULT_ADDRESS);

Thanks again - this forum is REALLY helpful

@micromet hmm… so your 2 sensors now work? Which fix did you use? With either my fix or one of @cermak’s (just changing the assignment of i2cAddr to i2cAddr = addr), I get each device having its proper address, but the one with the non-default address still can’t be found.

Just used @cermak’s assignment fix
(just changing the assignment of i2cAddr to i2cAddr = addr)
I tried several different jumper combinations/addresses, sometimes leaving one blank using the default, sometimes not.
Got good results.
here is my hardware

Ok, I think my problem was just a bad wire or a loose connection in my breadboard, which is why I was occasionally getting it to work. Changing the wire (connecting A0 on the MCP9808 to the power rail) and which hole I was plugging into seems to have fixed the problem.

This bug in the Particle library still exists as of 5 July 2020. Changes to the library must be made as indicated above to accept other I2C addresses besides 0x18.

It looks the contributor has abandoned the library.
There already is a issue report from 2017 but the repository owner has not reacted.

@marekparticle, can this library be taken over?

Good question, @ScruffR - let me check internally about our protocol here.