MCP9808 High Precision Temperature Sensor

Discuss development of drivers for external hardware and components, such as LCD screens, sensors, motor drivers, etc.
Target audience: Users and developers of drivers.
Post Reply
User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

MCP9808 High Precision Temperature Sensor

Post by kfricke » Sun Feb 07, 2016 12:17 am

Feel free to use this for anything you want...

Code: Select all

REG_CFG    = const(1)
REG_A_TEMP = const(5)
REG_M_ID   = const(6)
REG_D_ID   = const(7)
REG_T_RES  = const(8)

T_RES_MIN = const(0)
T_RES_LOW = const(1)
T_RES_AVG = const(2)
T_RES_MAX = const(3)

class MCP9808(object):
    def __init__(self, i2c=None, addr=0x18):
        assert i2c != None, 'No I2C object given!'
        self._i2c = i2c
        self._addr = addr
        self._check_device()

    def _send(self, buf):
        self._i2c.send(buf, self._addr)
        
    def _recv(self, n=2):
        return self._i2c.recv(n, self._addr)
    
    def _check_device(self):
        self._send(REG_M_ID)
        self._m_id = self._recv(2)
        if not self._m_id == b'\x00T':
            raise Exception("Invalid manufacturer ID: '%s'!" % self._m_id)
        self._send(REG_D_ID)
        self._d_id = self._recv(2)
        if not self._d_id == b'\x04\x00':
            raise Exception("Invalid device or revision ID: '%s'!" % self._d_id)
        
    # Set sensor into shutdown mode to draw less than 1 uA and disable 
    # continous temperature conversion. When not in shutdown mode the sensor
    # does draw 200-400 uA.
    def set_shutdown(self, shdn=True):        
        self._send(REG_CFG)
        cfg = self._recv(2)
        b = bytearray()
        b.append(REG_CFG)
        if shdn:
            b.append(cfg[0] | 1)
        else:
            b.append(cfg[0] & ~1)
        b.append(cfg[1])
        self._send(b)
        
    # Read temperature in degree celsius and return float value
    def read(self):
        self._send(REG_A_TEMP)
        raw = self._recv(2)
        u = (raw[0] & 0x0f) << 4
        l = raw[1] / 16
        if raw[0] & 0x10 == 0x10:
            temp = 256 - (u + l)
        else:
            temp = u + l
        return temp
    
    # Read a temperature in degree celsius and return a tuple of two parts.
    # The first part is the decimal patr and the second the fractional part 
    # of the value.
    # This method does avoid floating point arithmetic completely to support 
    # plattforms missing float support.
    def read_int(self):
        self._send(REG_A_TEMP)
        raw = self._recv(2)
        u = (raw[0] & 0xf) << 4
        l = raw[1] >> 4
        if raw[0] & 0x10 == 0x10:
            temp = 256 - (u + l)
            frac = 256 - (raw[1] & 0x0f) * 100 >> 4
        else:
            temp = u + l
            frac = (raw[1] & 0x0f) * 100 >> 4
        return temp, frac
        
    # Sets the temperature resolution. Higher resolutions do yield longer 
    # conversion times:
    #   Mode        Resolution  Conversion time
    # ------------------------------------------
    #   T_RES_MIN   0.5°C        30 ms
    #   T_RES_LOW   0.25°C       65 ms
    #   T_RES_AVG   0.125°C     130 ms
    #   T_RES_MAX   0.0625°C    250 ms
    #
    # On power up the mode is set to maximum resolution.
    def set_resolution(self, r):
        assert r in [0, 1, 2, 3], 'Invalid temperature resolution requested!'
        b = bytearray()
        b.append(REG_T_RES)
        b.append(r)
        self._send(b)

User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

Re: MCP9808 High Precision Temperature Sensor

Post by kfricke » Mon Feb 08, 2016 7:43 am

Now reasonably checked in on GitHub https://github.com/kfricke/micropython-mcp9808

Damien
Site Admin
Posts: 626
Joined: Mon Dec 09, 2013 5:02 pm

Re: MCP9808 High Precision Temperature Sensor

Post by Damien » Tue Feb 09, 2016 5:05 pm

Awesome, thank you!

User avatar
kfricke
Posts: 342
Joined: Mon May 05, 2014 9:13 am
Location: Germany

Re: MCP9808 High Precision Temperature Sensor

Post by kfricke » Tue Feb 09, 2016 7:11 pm

Just the little bit I can contribute. Keep up the great work!

User avatar
patvdleer
Posts: 46
Joined: Mon Jun 13, 2016 11:52 am
Location: Maastricht, NL, Europe
Contact:

Re: MCP9808 High Precision Temperature Sensor

Post by patvdleer » Tue Jun 14, 2016 10:35 am

Thanks!

I'm getting mine today delivered, should be a huge improvement over the DHT11s.
NodeMCU v0.9 / V1 / V2 / V3
WeMos D1 Mini
WeMos Lolin32 v1.0.0
WeMos Lolin D32 Pro V2

User avatar
patvdleer
Posts: 46
Joined: Mon Jun 13, 2016 11:52 am
Location: Maastricht, NL, Europe
Contact:

Re: MCP9808 High Precision Temperature Sensor

Post by patvdleer » Tue Jun 14, 2016 10:19 pm

I've updated the code to work on an ESP8266 as well, the I2C works a bit different...
https://github.com/patvdleer/micropython-mcp9808
NodeMCU v0.9 / V1 / V2 / V3
WeMos D1 Mini
WeMos Lolin32 v1.0.0
WeMos Lolin D32 Pro V2

User avatar
Petri
Posts: 6
Joined: Sun Dec 11, 2016 11:29 am
Location: Helsinki, Finland
Contact:

Re: MCP9808 High Precision Temperature Sensor

Post by Petri » Sun Dec 11, 2016 12:38 pm

EDIT: solution found to the issues below: on WiThumb, the internal pull-up resistors have to be enabled by using Pin.PULL_UP. More details at https://github.com/ThomasCLee/funnyvale/issues/3

-----------------

I am trying to read from MCP9808 on a WiThumb (https://funnyvale.com/withumb/). I have managed to install and test MicroPython (build from http://www.kaltpost.de/~wendlers/micropython/) on it.

If you have a WiThumb, here's how to get MicroPython on it: https://koodaamo.wordpress.com/2016/12/ ... umb-on-osx.

From the schematics and some other docs it I determined the I2C address for the temperature sensor on WiThumb is 0x1F rather than the commonly used 0x18.

I first set up I2C: i2c = I2C(scl=Pin(5), sda=Pin(4), freq=10000). No problem.

But then, i2c.readfrom_mem(0x1F, 5, 2) always returns b'\x00\x00'. No matter what register I try instead of 5, same result (tried all 1-16). Shouldn't it return something else to represent a temperature value? And AFAIK the other registers should return a manufacturer id (register 6) and device id (7)... What am I doing wrong or missing?

For what it's worth, the i2c object has following methods available: ['init', 'scan', 'start', 'stop', 'readinto', 'write', 'readfrom', 'readfrom_into', 'writeto', 'readfrom_mem', 'readfrom_mem_into', 'writeto_mem']. Doing scan() returns a whole bunch of values (25 or so of them), including the 0x1F mentioned above.

User avatar
Mike Teachman
Posts: 80
Joined: Mon Jun 13, 2016 3:19 pm
Location: Victoria, BC, Canada

Re: MCP9808 High Precision Temperature Sensor

Post by Mike Teachman » Mon Mar 05, 2018 12:53 am

Thanks for this driver ! I successfully used it for an outdoor solar installation. Having the shutdown mode implementation was great to conserve battery energy.

I'd like to mention a possible bug for temperatures <0 degC. During sub-zero nights I started noticing that the temperature always reads above freezing. I tracked the issue to get_temp(). The device implements temperature with 2's complement, so I think get_temp() should subtract 256. With the code below I see correct readings for temperatures below 0 degC. One note: The datasheet for this product presents a misleading implementation, both in Equation 5-1 and in the example code.

Code: Select all

  def get_temp(self):
        """
        Read temperature in degree celsius and return float value.
        """
        self._send(R_A_TEMP)
        raw = self._recv(2)
        u = (raw[0] & 0x0f) << 4
        l = raw[1] / 16
        if raw[0] & 0x10 == 0x10:
            temp = (u + l) - 256.0
        else:
            temp = u + l
        return temp

BruinBear
Posts: 27
Joined: Sat Jan 14, 2017 11:59 am

Re: MCP9808 High Precision Temperature Sensor

Post by BruinBear » Sun May 06, 2018 9:17 am

Mike
You are correct. Since you haven't had a reply here, maybe you should raise an issue on GitHub so the author can correct it?

T-Wilko
Posts: 9
Joined: Thu Sep 19, 2019 8:08 am

Re: MCP9808 High Precision Temperature Sensor

Post by T-Wilko » Thu Feb 06, 2020 5:22 am

EDIT: To get this to run with the current (1.12 at time of writing) micropython version's machine class, the following code must be used:

Code: Select all


from machine import I2C

R_CFG    = const(1)
R_B_UP   = const(2)
R_B_LOW  = const(3)
R_B_CRIT = const(4)
R_A_TEMP = const(5)
R_M_ID   = const(6)
R_D_ID   = const(7)
R_T_RES  = const(8)

T_RES_MIN = const(0)
T_RES_LOW = const(1)
T_RES_AVG = const(2)
T_RES_MAX = const(3)

class MCP9808(object):
    """
    This class implements an interface to the MCP9808 temprature sensor from
    Microchip.
    """

    def __init__(self, i2c=None, addr=0x18):
        """
        Initialize a sensor object on the given I2C bus and accessed by the
        given address.
        """
        if i2c == None or i2c.__class__ != I2C:
            raise ValueError('I2C object needed as argument!')
        self._i2c = i2c
        self._addr = addr
        self._check_device()

    def _send(self, buf):
        """
        Sends the given buffer object over I2C to the sensor.
        """
        self._i2c.writeto(self._addr, bytearray([buf]))

    def _recv(self, n):
        """
        Read bytes from the sensor using I2C. The byte count must be specified
        as an argument.
        Returns a bytearray containing the result.
        """
        return self._i2c.readfrom(self._addr, n)

    def _check_device(self):
        """
        Tries to identify the manufacturer and device identifiers.
        """
        self._send(R_M_ID)
        self._m_id = self._recv(2)
        if not self._m_id == b'\x00T':
            raise Exception("Invalid manufacturer ID: '%s'!" % self._m_id)
        self._send(R_D_ID)
        self._d_id = self._recv(2)
        if not self._d_id == b'\x04\x00':
            raise Exception("Invalid device or revision ID: '%s'!" % self._d_id)

    def set_shutdown_mode(self, shdn=True):
        """
        Set sensor into shutdown mode to draw less than 1 uA and disable
        continous temperature conversion.
        """
        if shdn.__class__ != bool:
            raise ValueError('Boolean argument needed to set shutdown mode!')
        self._send(R_CFG)
        cfg = self._recv(2)
        b = bytearray()
        b.append(R_CFG)
        if shdn:
            b.append(cfg[0] | 1)
        else:
            b.append(cfg[0] & ~1)
        b.append(cfg[1])
        self._send(b)

    def get_temp(self):
        """
        Read temperature in degree celsius and return float value.
        """
        self._send(R_A_TEMP)
        raw = self._recv(2)
        u = (raw[0] & 0x0f) << 4
        l = raw[1] / 16
        if raw[0] & 0x10 == 0x10:
            temp = 256 - (u + l)
        else:
            temp = u + l
        return temp

    def get_temp_int(self):
        """
        Read a temperature in degree celsius and return a tuple of two parts.
        The first part is the decimal patr and the second the fractional part
        of the value.
        This method does avoid floating point arithmetic completely to support
        plattforms missing float support.
        """
        self._send(R_A_TEMP)
        raw = self._recv(2)
        u = (raw[0] & 0xf) << 4
        l = raw[1] >> 4
        if raw[0] & 0x10 == 0x10:
            temp = 256 - (u + l)
            frac = 256 - (raw[1] & 0x0f) * 100 >> 4
        else:
            temp = u + l
            frac = (raw[1] & 0x0f) * 100 >> 4
        return temp, frac

    def set_resolution(self, r):
        """
        Sets the temperature resolution.
        """
        if r not in [T_RES_MIN, T_RES_LOW, T_RES_AVG, T_RES_MAX]:
            raise ValueError('Invalid temperature resolution requested!')
        b = bytearray()
        b.append(R_T_RES)
        b.append(r)
        self._send(b)

EDIT OVER

*************************

Hi all, I realise this is a bit of an old thread but I've decided to use this sensor since it was lying around and I've encountered a problem. When I run the given driver code, I run into an error when initialising object:

tempsensor = mcp9808.MCP9808(i2c)

which yields " 'I2C' object has no attribute 'send' "

I suppose this was written with an older version of micropython, and due to my limited run with micropython (especially in writing hardware drivers) I'm not sure how to amend this.
My first thought would be to change line 38 from self._i2c.send(buf, self._addr) to self._i2c.writeto(buf, self._addr) which then throws another error of an object with a buffer protocol being required.

Any advice/direction would be much appreciated

Post Reply