Wire.h Compilation Errors (SCD40 Sensor)

Apologies for the long read, I am trying to learn and make sense of this as I go. I am trying to use a SDC40 sensor over I2C to read CO2, temperature, and humidity. Originally I thought my errors may have been a conflict with an existing instance of Wire, however, the example sketch provided within the library "Sensirion I2C SCD4x" by Sensirion wont compile either.

If I switch the board from Arduino Uno WiFi Rev2 to something in the AVR range such as the standard Arduino Uno Wifi then everything compiles just fine. I am assuming something with the megaAVR boards conflicts with how this sensor expects to use I2C.

Compilation Errors:

In file included from C:\Users\d4sturt\Documents\Arduino\libraries\Sensirion_Core\src\SensirionI2CCommunication.h:38:0,
                 from C:\Users\d4sturt\Documents\Arduino\libraries\Sensirion_Core\src\SensirionI2CCommunication.cpp:31:
C:\Users\d4sturt\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\libraries\Wire\src/Wire.h: In static member function 'static uint16_t SensirionI2CCommunication::receiveFrame(uint8_t, size_t, SensirionI2CRxFrame&, TwoWire&, CrcPolynomial)':
C:\Users\d4sturt\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\libraries\Wire\src/Wire.h:64:12: note: candidate 1: size_t TwoWire::requestFrom(int, int, int)
     size_t requestFrom(int, int, int);
            ^~~~~~~~~~~
C:\Users\d4sturt\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\libraries\Wire\src/Wire.h:62:12: note: candidate 2: virtual size_t TwoWire::requestFrom(uint8_t, size_t, bool)
     size_t requestFrom(uint8_t, size_t, bool);
            ^~~~~~~~~~~

When I look into the Class found in "SensirionI2CCommunication.h" I see:

class SensirionI2CCommunication {
  public:
    static uint16_t sendFrame(uint8_t address, SensirionI2CTxFrame& frame,
                              TwoWire& i2cBus); 
    static uint16_t receiveFrame(uint8_t address, size_t numBytes,
                                 SensirionI2CRxFrame& frame, TwoWire& i2cBus,
                                 CrcPolynomial poly = CRC31_ff);
};

The sensor is supposed to return CO2, temperature, and humidity. In the sketch they are defined as uint16_t, float, and float respectively. Looking at the function which handles sending/receiving I can see that all values are declared as uint16_t and then later converted to match the expected data types:

uint16_t SensirionI2CScd4x::readMeasurementTicks(uint16_t& co2,
                                                 uint16_t& temperature,
                                                 uint16_t& humidity) {
    uint16_t error;
    uint8_t buffer[9];
    SensirionI2CTxFrame txFrame(buffer, 9);

    error = txFrame.addCommand(0xEC05);
    if (error) {
        return error;
    }

    error = SensirionI2CCommunication::sendFrame(SCD4X_I2C_ADDRESS, txFrame,
                                                 *_i2cBus);
    if (error) {
        return error;
    }

    delay(1);

    SensirionI2CRxFrame rxFrame(buffer, 9);
    error = SensirionI2CCommunication::receiveFrame(SCD4X_I2C_ADDRESS, 9,
                                                    rxFrame, *_i2cBus);
    if (error) {
        return error;
    }

    error |= rxFrame.getUInt16(co2);
    error |= rxFrame.getUInt16(temperature);
    error |= rxFrame.getUInt16(humidity);
    return error;
}

uint16_t SensirionI2CScd4x::readMeasurement(uint16_t& co2, float& temperature,
                                            float& humidity) {
    uint16_t error;
    uint16_t temperatureTicks;
    uint16_t humidityTicks;

    error = readMeasurementTicks(co2, temperatureTicks, humidityTicks);
    if (error) {
        return error;
    }

    temperature = static_cast<float>(temperatureTicks * 175.0 / 65535.0 - 45.0);
    humidity = static_cast<float>(humidityTicks * 100.0 / 65535.0);
    return NoError;
}

If I am interpreting the errors correctly then Wire is expecting the result to be all integers as referenced as the first candidate in the error description, while Sensirion is expecting all uint16_t's.

I am also going to assume that the second candidate in the output window is only for a single frame of data (uint8_t, size_t, bool). Not sure if this is correct with my limited knowledge of how Wire and I2C work.

At this point I do not know what to change or if using this sensor is even feasible. Please let me know if there is any additional detail needed or more importantly...a solution.

:arrow_right: It is the parameters of the functions of the Wire library.

Welcome to the forum.
You can still edit your post, for example for small corrections, such as SDC40 -> SCD40.

Arduino Uno WiFi Rev2: https://store.arduino.cc/products/arduino-uno-wifi-rev2
That is a 5V board with a ATmega4809.
The SCD40 can run at 5V, so that is good, you have a 5V I2C bus.

The "Wire.h" library for the "megaavr" branch is this one: https://github.com/arduino/ArduinoCore-megaavr/blob/master/libraries/Wire/src/Wire.h

Spot the difference:

"megaavr" possibilities for requestFrom() in Wire.h:

    size_t requestFrom(uint8_t, size_t);
    size_t requestFrom(uint8_t, size_t, bool);
    size_t requestFrom(int, int);
    size_t requestFrom(int, int, int);

"avr" possibitilies for requestFrom() in Wire.h (I removed the undocumented one):

    uint8_t requestFrom(uint8_t, uint8_t);
    uint8_t requestFrom(uint8_t, uint8_t, uint8_t);
    uint8_t requestFrom(int, int);
    uint8_t requestFrom(int, int, int);

You have to change the Sensirion library. You can cast the parameters to 'int'.

The first compiler warning is the most important. Find the first error and fix the library for the parameters of Wire.requestFrom().

Conclusion: It is not your fault, it is the fault of Arduino because it should be compatible for different platforms and it is not.

1 Like

I was afraid that would be the answer, thank you for the detailed reply. This is one of those scenarios where hindsight is 20/20, had I known about the differences I would have selected a different board with the avr architecture instead of megaavr.

I narrowed it down to this line: arduino-core/src/SensirionI2CCommunication.cpp at main · Sensirion/arduino-core · GitHub

readAmount = i2cBus.requestFrom(address, static_cast<uint8_t>(numBytes),
                                static_cast<uint8_t>(true));

They used too many "static_cast" in the code, even for normal numbers. Using "static_cast" is meaningful with C++ templates and it is better when doing a complex cast. Sadly, it is a hype to use "static_cast" because it looks interesting.

You could try to find the file "SensirionI2CCommunication.cpp" on your computer in the "Documents \ Arduino \ libraries" folder and change it to:

readAmount = i2cBus.requestFrom( (int) address, (int) numBytes);

I did not use the third parameter, because the third parameter for Wire.requestFrom() is not used :exploding_head:

1 Like

Koepel, you are my hero. I was able to update it and it compiled without issue. Once I get a moment to test it I will update this post with the results.

Looks like that change was all that was needed. I can't say thank you enough, I was trying to work my way through the code to see what I needed to change. It has been years since I have done any programming outside of PLC's so my C++ is lacking. I really want to understand how you narrowed it down to that line but in no way do I expect you to explain, just means I need to do some legwork to get familiar again.

image

I have seen that problem before, and the compiler tells what the problem (if you understand compiler-talk :wink: ).

When I searched for that Sensirion library, I found this: https://github.com/Sensirion/arduino-i2c-scd4x
I used Github to search for "requestFrom", but it wasn't there. In the readme they say that it depends on Sensirion Core and there was the "requestFrom", and all the over-the-top use of "static_cast".

Some people prefer to use 'int' for almost everything, because a readable source code is the most important.
Some people prefer to use 'byte' whenever that is possible, because they are afraid that they waste a byte. These are the same people that use "static_cast" in every place because they want to show off how good they are in not wasting bytes. But they never actually measured if it matters for the amount of used memory.

For everyone else: I'm not exaggerating. When I see this: "static_cast<uint8_t>(true)" then I go :face_vomiting: and it is for the third parameter of Wire.requestFrom() which is never used anyway, so double :face_vomiting: :face_vomiting:

Ah yes, I was not interpreting the compiler information as I should have. I was looking at:

C:\Users\d4sturt\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\libraries\Wire\src/Wire.h: In static member function 'static uint16_t SensirionI2CCommunication::receiveFrame(uint8_t, size_t, SensirionI2CRxFrame&, TwoWire&, CrcPolynomial)'

When I should have been looking for

C:\Users\d4sturt\AppData\Local\Arduino15\packages\arduino\hardware\megaavr\1.8.8\libraries\Wire\src/Wire.h:64:12: note: candidate 1: size_t TwoWire::requestFrom(int, int, int)
     size_t requestFrom(int, int, int);

Looking at the type and size of the data being received it does seem odd to implement it this way, especially since every value from the sensor is stored as a uint16_t and later converted to int and float. The need for static cast seems irrelevant in this case as at most they would save is 1 byte, even then, that 1 byte almost never would be saved as the limit would be 255 as a uint8_t. The ONLY time that byte would be saved is if the readings from any of the 3 sensors is less than 255, which would equate to a humidity level of less than 0.389% or temp of around 0.69 C... That is if I am doing my math right:

uint16_t SensirionI2CScd4x::readMeasurement(uint16_t& co2, float& temperature,
                                            float& humidity) {
    uint16_t error;
    uint16_t temperatureTicks;
    uint16_t humidityTicks;

    error = readMeasurementTicks(co2, temperatureTicks, humidityTicks);
    if (error) {
        return error;
    }

    temperature = static_cast<float>(temperatureTicks * 175.0 / 65535.0 - 45.0);
    humidity = static_cast<float>(humidityTicks * 100.0 / 65535.0);

I'm one of them :wink:

I don't because I have no idea about C++ :rofl:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.