0

I am using an infrared sensor called OTI301. In its data sheet it says that in order to obtain object temperature and ambient temperature values I need to extract the binary information from the sensor and use the binary information in given formulas. I have done all that but for some reason I am not getting the right values for the object temperature. The ambient temperature seems to work well but when I put a hot object, more than 320 F, the formula returns a random negative number. Also the the sensor can go up to 780 F so I know the temperature has not been capped. Please see pictures for important data sheet information, or here are the links for the complete data sheets https://drive.google.com/file/d/1XMRDCNzY3fn0q6lkGejs5D-pqksfRuZ8/view?usp=sharing. https://drive.google.com/file/d/1mmdkHkNbwC5VgQxGMrq07XolajNvgBkN/view?usp=sharing. Lastly, here is the part of the code where I extract the information and apply formulas.

     void loop() 
 {
   if (Wire.read()) // On success, read() will return 1, on fail 0.
   {
     buttonState = digitalRead(button);
     //Serial.println(buttonState);
     Wire.beginTransmission(DEVICE_ADDRESS);
     Wire.write(0x80);  // readout command 
     Wire.endTransmission(false); 
     Wire.requestFrom(DEVICE_ADDRESS, 6); // request 6 bytes
     uint8_t data[6];
     for (int i = 0; i < 6; i++) {
         data[i] = Wire.read();
     }

      //Calculate Object Temperature
     if(data[5] < 0x80) {
       TA = (data[3]+ data[4]*256+data[5]*65536)/200;  
     }
     else {
       TA = (((data[3]+ data[4]*256+data[5]*65536)) - 16777216) /200;  
     }

     //Calculate Ambient Temperature
     if(data[2] < 0x80) {
       TA = (data[0]+ data[1]*256+data[2]*65536)/200;
      }
     else {
       TA = (((data[0]+ data[1]*256+data[2]*65536)) - 16777216) /200;
     }

enter image description here

  • 1
    What data type is TA? – Majenko Nov 23 '19 at 23:36
  • on a nano, int calculations are 16 bit. A multiplication by *65536 wont work as expected if you don't force it into 32bit arithmetics. – DataFiddler Nov 24 '19 at 0:26
  • @Majenko I defined TA as a float. – Santiago Restrepo Serna Nov 24 '19 at 0:40
  • When you calculate TA you are doing integer math. Try as DataFiddler wrote and cast the numbers to long, so that the result cannot overflow – chrisl Nov 24 '19 at 13:41
  • @DataFiddler: A multiplication by 65536 will work as expected, because 65536 is a 32-bit long int. – Edgar Bonet Nov 24 '19 at 20:13
1

You said "I defined TA as a float." That doesn't matter. In C/C++, expressions are only "promoted" to a larger/different data type AFTER they are evaluated. Your calculations are being done using 16-bit ints, which overflow, and then cast to a float.

(C/C++ evaluates expressions from the inner-most outward, and if one of the operands to an operator is a larger type, it will "promote" the other operand to the larger type before doing a calculation. It won't notice that a calculation will overflow and promote the operands to fix that problem however.)

Try this:

   TA = (data[3]+ data[4]*256+uint64(data[5])*65536)/200;  

That version of the code forces data[5] to an unsigned long before multiplying it by 65536.

Do the same thing to the value that you multiply by 65536 in all of your expressions and you should avoid overflow.

| improve this answer | |
  • Thank you all. problem solved! – Santiago Restrepo Serna Nov 24 '19 at 16:30
  • 1. On the Nano, uint64 (I guess you mean uint64_t) is not the same as unsigned long: the latter is 32-bits only. 2. The overflow happens with data[4], not with data[5]. 3. Your formula gives grossly bad results for temperatures larger than 163.84 °C (try data = {0,0,0,0,128,0}). – Edgar Bonet Nov 24 '19 at 21:36
  • overflow on data(4)? Is data an int or a byte? – Duncan C Nov 24 '19 at 22:04
1

The rules of integer arithmetics are tricky in C++. There are rules that give the type of integer literals:

  • 256 and 200 are of type int, which is the default type for integer literals. On the Nano, this type is 16-bits, and covers the range from −32,768 to +32,767.

  • 65536 being too large for the int type, it is implicitly considered a long int. This type is 32-bits on the Nano, and covers roughly ±2.15×109.

Then there are rules for the types used in arithmetic operations. No computation is ever done on a type smaller than int: anything smaller is promoted to int. Then, if you combine two operands of different types, the larger type “wins” and the compiler implicitly converts the other operand to the larger type. Note that if two types have the same bit size, the unsigned one is considered “larger”.

Let's go now through your formula, and see the types of the different terms:

data[3]       = uint8_t
data[4]*256   = uint8_t * int → int
data[5]*65536 = uint8_t * (long int) → long int

Now you may see the problem. Your calculation will eventually overflow, but the overflow is not in data[5]*65536, as one may naively expect. What happens is than when the temperature reaches 163.84 °C (≈ 327 °F), data[4] becomes 128, and data[4]*256 is 32,768, just too large for an int. The result overflows to −128, hence the negative values you see.

The simplest solution is to force 256 to be a long int, by appending the L suffix to the number. Then the multiplication will be performed with the long int type, and there will be no overflow:

TA = (data[3] + data[4]*256L + data[5]*65536) / 200;

On a final note, it is worth noting that the numbers 256, 65536 and 16777216 used in the formula are all powers of two. Even powers of 256. You may notice that, save for the division by 200, what this formula describes is the coding of an integer as a 24-bit, little-endian, two's complement number. This format (two's complement little-endian) is exactly what the Arduino uses internally for representing numbers. You could then avoid all those calculations and get your result by just telling the compiler that what those memory cells are actually holding is a 24-bit integer:

float TA = *(__int24*)&data[3] / 200.0;

This works for both positive and negative temperatures, but it has the drawback of breaking some C++ rules (aliasing rules), and using the non-standard type __int24. A safest way to achieve the same is to build a 32-bit integer by putting the bits in place using bitwise operations. But note that then a sign-extension byte is needed to convert the 24-bit number to 32 bits:

uint8_t sign_extension = data[5] & 0x80 ? 0xff : 0x00;
int32_t reading = (uint32_t) data[3] <<  0
                | (uint32_t) data[4] <<  8
                | (uint32_t) data[5] << 16
                | (uint32_t) sign_extension << 24;
float TA = reading / 200.0;
| improve this answer | |
  • int32_t reading = (int32_t)(int8_t)b[5] <<16 | (uint16_t)b[4] << 8 | b[3] ; the highbyte just has to be signed, then it's still tricky but a bit simpler. – DataFiddler Nov 24 '19 at 23:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.