1
votes

First off, sorry for the confusing title. It's pretty late here and I wasn't able to come up with a better one.

So, I have a I2C temperature sensor that outputs the current temperature as a 16 bit word. Reading from LEFT to RIGHT, the 1st bit is the MSB and the 13th bit is the LSB, so 13 bits are payload and the last 3 bits are zeros. I want to read out that sensor with a Raspberry Pi and convert the data.

The first byte (8 bits) are the integer part of the current temperature. If and only if the temperature is negative, the two's complement of the entire word has to be built.

the second byte is the decimal part which has to be multiplied by 0.03125.

So, just a couple of examples (TEMP DIGITAL OUTPUT (Binary) / DIGITAL OUTPUT (Hex), taken from the data sheet here http://datasheets.maximintegrated.com/en/ds/DS1624.pdf)

 +125˚C    | 01111101 00000000 | 7D00h
+25.0625˚C | 00011001 00010000 | 1910h
+½˚C       | 00000000 10000000 | 0080h
0˚C        | 00000000 00000000 | 0000h
-½˚C       | 11111111 10000000 | FF80h
-25.0625˚C | 11100110 11110000 | E6F0h
-55˚C      | 11001001 00000000 | C900h

Because of a difference in endianness the byte order is reversed when reading the sensor, which is not a problem. For example, the first line would become 0x007D instead of 0x7D00, 0xE6F0 becomes F0E6, and so on...

However, once I build the two's complement for negative values I'm not able to come up with a correct conversion.

What I came up with (not working for negative values) is:

import smbus
import time
import logging

class TempSensor:
"""

Class to read out an DS1624 temperature sensor with a given address.

DS1624 data sheet: http://datasheets.maximintegrated.com/en/ds/DS1624.pdf

Usage:

    >>> from TempSensor import TempSensor
    >>> sensor = TempSensor(0x48)
    >>> print "%02.02f" % sensor.get_temperature()
    23.66

"""

# Some constants
DS1624_READ_TEMP = 0xAA
DS1624_START = 0xEE
DS1624_STOP = 0x22

def __init__(self, address):
    self.address = address
    self.bus = smbus.SMBus(0)

def __send_start(self):
    self.bus.write_byte(self.address, self.DS1624_START);

def __send_stop(self):
    self.bus.write_byte(self.address, self.DS1624_STOP);

def __read_sensor(self):         
    """    
    Gets the temperature data. As the DS1624 is Big-endian and the Pi Little-endian, 
    the byte order is reversed.

    """            

    """
    Get the two-byte temperature value. The second byte (endianness!) represents
    the integer part of the temperature and the first byte the fractional part in terms
    of a 0.03125 multiplier.
    The first byte contains the value of the 5 least significant bits. The remaining 3
    bits are set to zero.
    """
    return self.bus.read_word_data(self.address, self.DS1624_READ_TEMP)        

def __convert_raw_to_decimal(self, raw):
    # Check if temperature is negative
    negative = ((raw & 0x00FF) & 0x80) == 0x80

    if negative:
        #  perform two's complement
        raw = (~raw) + 1

    # Remove the fractional part (first byte) by doing a bitwise AND with 0x00FF
    temp_integer = raw & 0x00FF

    # Remove the integer part (second byte) by doing a bitwise AND with 0XFF00 and
    # shift the result bits to the right by 8 places and another 3 bits to the right 
    # because LSB is the 5th bit          
    temp_fractional = ((raw & 0xFF00) >> 8) >> 3

    return temp_integer + ( 0.03125 * temp_fractional)

def run_test(self):
    logging.basicConfig(filename='debug.log', level=logging.DEBUG)

    # Examples taken from the data sheet (byte order swapped)
    values = [0x7D, 0x1019, 0x8000, 0, 0x80FF, 0xF0E6, 0xC9]

    for value in values:
        logging.debug('value: ' + hex(value) + ' result: ' + str(self.__convert_raw_to_decimal(value)))

def get_temperature(self):
    self.__send_start();
    time.sleep(0.1);
    return self.__convert_raw_to_decimal(self.__read_sensor())

If you run the run_test() method you'll see what i mean. All negatives values are wrong.

The results I get are:

DEBUG:root:value: 0x7d result: 125.0
DEBUG:root:value: 0x1019 result: 25.0625
DEBUG:root:value: 0x8000 result: 0.5
DEBUG:root:value: 0x0 result: 0.0
DEBUG:root:value: 0x80ff result: 1.46875
DEBUG:root:value: 0xf0e6 result: 26.03125
DEBUG:root:value: 0xc9 result: 55.96875

So, I've been banging my head for hours on this one, but it seems I'm lacking the fundamentals of bit-wise operations. I believe that the problem is the masking with the logical AND when values are negative.

EDIT: There are a couple of implementations on the web. None of them works for negative temperatures. I tried it by actually putting the sensor in ice water. I haven't tried the Arduino C++ version yet, but from looking at the source code it seems it doesn't build the two's complement at all, so no negative temperatures either (https://github.com/federico-galli/Arduino-i2c-temperature-sensor-DS1624/blob/master/DS1624.cpp).

1

1 Answers

3
votes

Two things, you've got your masks turned around, raw & 0x00ff is the fractional part, not the integer part, and second, this is my solution, given the inputs in your table, this seems to work for me:

import struct

def convert_temp (bytes):
    raw_temp = (bytes & 0xff00) >> 8
    raw_frac = (bytes & 0x00ff) >> 3

    a, b = struct.unpack('bb', '{}{}'.format(chr(raw_temp), chr(raw_frac)))
    return a + (0.03125 * b)

The struct module is really nifty when working with more basic data types (such as signed bytes). Hope this helps!

Edit: ignore the comment on your masks, I see my own error now. You can switch around the bytes, should be no problem.

Struct explanation: Struct.(un)pack both take 2 arguments, the first is a string that specified the attributes of your struct (think in terms of C). In C a struct is just a bunch of bytes, with some information about their types. The second argument is the data that you need decoded (which needs to be a string, explaining the nasty format()).

I can't seem to really explain it any further, I think if you read up on the struct module, and structs in C, and realize that a struct is nothing more then a bunch of bytes, then you should be ok :).

As for the two's complement, that is the regular representation for a signed byte, so no need to convert. The problem you where having is that Python doesn't understand 8-bit integers, and signedness. For instance, you might have a signed byte 0x10101010, but if you long() that in Python, it doesn't interpret that as a signed 8-bit int. My guess is, it just puts it inside and 32-bit int, in which case the sign bit gets interpretted as just the eighth bit.

What struct.unpack('b', ...) does, is actually interpret the bits as a 8-bit signed integer. Not sure if this makes it any clearer, but I hope it helps.