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;
}
2 Answers 2
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.
-
Thank you all. problem solved!Santiago Restrepo Serna– Santiago Restrepo Serna2019年11月24日 16:30:00 +00:00Commented Nov 24, 2019 at 16:30
-
1. On the Nano,
uint64
(I guess you meanuint64_t
) is not the same asunsigned long
: the latter is 32-bits only. 2. The overflow happens withdata[4]
, not withdata[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– Edgar Bonet2019年11月24日 21:36:12 +00:00Commented Nov 24, 2019 at 21:36 -
overflow on data(4)? Is data an int or a byte?Duncan C– Duncan C2019年11月24日 22:04:19 +00:00Commented Nov 24, 2019 at 22:04
The rules of integer arithmetics are tricky in C++. There are rules that give the type of integer literals:
256
and200
are of typeint
, 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 theint
type, it is implicitly considered along int
. This type is 32-bits on the Nano, and covers roughly ×ばつ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;
-
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– DataFiddler2019年11月24日 23:32:21 +00:00Commented Nov 24, 2019 at 23:32
TA
?TA
you are doing integer math. Try as DataFiddler wrote and cast the numbers to long, so that the result cannot overflowlong int
.