Friday 22 February 2013

12bit Result From DS18S20

During my development using the DallasTemperature library to build my temperature web server I noticed that there is no API to get the 12 bit word value of the temperature reading and all you can get is a float value in Celsius or Fahrenheit.
Although most of the time it make sense to get a float value to render, a float value use 32 bit and will reduce the amount of readings I can store in RAM or EEPROM by half (or even more if I use 3 bytes to store two 12 bit values).

Getting the 12 bit value for DS12B20, DS1822 or DS1225 is simple, just combine byte 0 (Temp LSB) and byte 1 (Temp MSB) of the device scratch pad:

int rawTemperature = (((int)scratchPad[TEMP_MSB]) << 8) | scratchPad[TEMP_LSB];

However, for the DS18S20, this will return the 9 bit result, and there is additional formula you need to apply to get the 12 bit result as specified in the documentation:

Resolutions greater than 9 bits can be calculated using the data from the temperature, COUNT REMAIN and COUNT PER °C registers in the scratchpad. Note that the COUNT PER °C register is hard-wired to 16 (10h). After reading the scratchpad, the TEMP_READ value is obtained by truncating the 0.5°C bit (bit 0) from the temperature data (see Figure 2). The extended resolution temperature can then be calculated using the following equation:

TEMPERATURE = TEMP_READ - 0.25 +
              (COUNT_PER_C - COUNT_REMAIN)/COUNT_PER_C
This was implemented in the library with the following statement:

return (float)(rawTemperature >> 1) - 0.25 + ((float)(scratchPad[COUNT_PER_C] - scratchPad[COUNT_REMAIN]) / (float)scratchPad[COUNT_PER_C]);

Apart from the performance impact of floating point calculations (see this), the result is a float number, and not the 12 bit I am looking for.

Let's implement it with integers to calculate the 1/16 of the Celsius degree value, this is the integer result we get from any other device.
  1. Truncating the 0.5 bit - use a simple & mask: raw & 0xFFFE
  2. Convert to 12 bit value (1/16 of °C) - shift left: (raw & 0xFFFE)<<3
  3. Subtracting 0.25 (1/4 °C of 1/16) or 0.25/0.0625 = 4: ((raw & 0xFFFE)<<3)-4
  4. Add the count (count per c - count remain), count per c is constant of 16, and no need to dived by 16 since we are calculating to the 1/16 of °C: +16 - COUNT_REMAIN
Full expression:
((rawTemperature & 0xFFFE) << 3) - 4 + 16 - scratchPad[COUNT_REMAIN]
We can simplify it to:
((rawTemperature & 0xFFFE) << 3) + 12 - scratchPad[COUNT_REMAIN]
The next step is to extend the library with a getTemp function that returns the raw 12 bit temperature value no matter what device you use:

// Construct the integer value
int16_t rawTemperature = (((int16_t)scratchPad[TEMP_MSB]) << 8) | scratchPad[TEMP_LSB];

// For DS18S20, use COUNT_REMAIN to calculate the 12 bit value
if (deviceAddress[0] == DS18S20MODEL) rawTemperature = ((rawTemperature & 0xFFFE) << 3) + 12 - scratchPad[COUNT_REMAIN];

// Retunr a 12 bit value
return rawTemperature; 

Then getTempC is nothing but division by 16:

return (float)getTemp() * 0.0625;

The actual code use static utility functions like rawToCelsius, that one can use to perform the conversion later on after storing the 12 bit raw value.

I will post the updated library later on after some QA.

UPDATE: This is now merged into the DallasTemperature library. Thank you Miles.

No comments:

Post a Comment