Pico W Micro Python for SHT30 Temp/Humidity HELP!

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Roberthh » Sun Aug 14, 2022 2:19 pm

I need to pull my yellow SCL off pin 5 and put it on to GP5 (Pin 7)?
I need to pull my green SDA off pin 4 and put it on to GP4 (Pin 6)?
Yes.
So, I need:
Vcc and SDA connected with a 10kohm resistor for 0x44
Vcc and SCL connected with a 10kohm resistor for 0x45

Is this correct?
No. The pull-up resistors on sda and scl are not related to the I2C address. These resistors are required for the communication.
It may be, that the internal pull-up resistors of the Pico are enabled. These are too weak for fast communication, but they may work for a low I2C freq. You can try:

Code: Select all

from machine import I2C, Pin

i2c=I2C(0, sda=Pin(4), scl=Pin(5), freq=1000)
print(i2c.scan())
That may work for now. If yes, you have to change the lib code as well.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Roberthh » Sun Aug 14, 2022 2:21 pm

Try a lower bus freq. i2c.scan() just transmits a single byte per address, so it may succeed, but longer messages will fail.

Rissy
Posts: 116
Joined: Sun Aug 14, 2022 8:15 am

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Rissy » Sun Aug 14, 2022 2:30 pm

Roberthh wrote:
Sun Aug 14, 2022 2:19 pm
I need to pull my yellow SCL off pin 5 and put it on to GP5 (Pin 7)?
I need to pull my green SDA off pin 4 and put it on to GP4 (Pin 6)?


Yes.

So, I need:
Vcc and SDA connected with a 10kohm resistor for 0x44
Vcc and SCL connected with a 10kohm resistor for 0x45

Is this correct?
Roberthh wrote:
Sun Aug 14, 2022 2:19 pm
No. The pull-up resistors on sda and scl are not related to the I2C address. These resistors are required for the communication.
It may be, that the internal pull-up resistors of the Pico are enabled. These are too weak for fast communication, but they may work for a low I2C freq. You can try:
I'm not sure i fully understand this i'll admit. Although it seems to be working now, can you please be clear and spell out the obvious to me and advise how to connect 10kohm resistors for fast communication please. once for address 0x44 and again for 0x45 please?
Roberthh wrote:
Sun Aug 14, 2022 2:19 pm

Code: Select all

from machine import I2C, Pin

i2c=I2C(0, sda=Pin(4), scl=Pin(5), freq=1000)
print(i2c.scan())
That may work for now. If yes, you have to change the lib code as well.
I tried this, and I tried it with multiple changes to the freq applied. I tried 100, 500, 1000, 10000 and 20000. All of them responded with "[68]" although obviously the response was much slower with the lower freq than the higher freq used. What does this mean? That I don't need a 10kohm resistor now?

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Roberthh » Sun Aug 14, 2022 2:40 pm

The pull up resistors do not change the address of your sensor. They change the slew rate of the rising edge in the communication line. So the resistors are required for both sda and scl. It is still possible, that you sensor has these already built-in.

You say, it works now, Do you talk about i2c.scan() or getting reasonable values from the sensor? If it is the latter even when you do not set freq, then you're. Then the sensor has built-in resistors. If you do not get reasonable values, but errors instead, try changing this line 40 of the by adding the freq=1000 parameter.

self.i2c = I2C(0, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=1000)

Rissy
Posts: 116
Joined: Sun Aug 14, 2022 8:15 am

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Rissy » Sun Aug 14, 2022 2:48 pm

Roberthh wrote:
Sun Aug 14, 2022 2:40 pm
The pull up resistors do not change the address of your sensor. They change the slew rate of the rising edge in the communication line. So the resistors are required for both sda and scl. It is still possible, that you sensor has these already built-in.
Ah right, I think i understand now (not "slew rate", but the difference between communications requirements versus addresses at least)
Roberthh wrote:
Sun Aug 14, 2022 2:40 pm
You say, it works now, Do you talk about i2c.scan() or getting reasonable values from the sensor? If it is the latter even when you do not set freq, then you're. Then the sensor has built-in resistors. If you do not get reasonable values, but errors instead, try changing this line 40 of the by adding the freq=1000 parameter.

self.i2c = I2C(0, scl=Pin(scl_pin), sda=Pin(sda_pin), freq=1000)
I only meant the response of "[68]" to the scan program. This works by changing the freq value to the values previously mentioned.

Trying to get readings from the sensor isn't working though. I always get the same error even after your suggestion of including the above freq value:

%Run -c $EDITOR_CONTENT
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
File "sht30.py", line 140, in measure
File "sht30.py", line 105, in send_cmd
SHT30Error: Bus error

I've now ordered a pack of 10 10kohm resistors (1/4 watt) from ebay. That will be days befor they arrive however. So if that's as far as I can go right now, then we'll have ro reconvene perhaps through the week again at some time.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Roberthh » Sun Aug 14, 2022 2:56 pm

OK. I looked into the code. It is a little bit old style by using i2c.start() and i2c.stop(). These are not needed and must be removed.

Rissy
Posts: 116
Joined: Sun Aug 14, 2022 8:15 am

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Rissy » Sun Aug 14, 2022 3:13 pm

Roberthh wrote:
Sun Aug 14, 2022 2:56 pm
OK. I looked into the code. It is a little bit old style by using i2c.start() and i2c.stop(). These are not needed and must be removed.
Oh right. I wouldn't have a clue about this! I've only been playing around in Linux, Python, Raspberry Pi for about a 3 weeks.

You'd have to give me guidance on what i need to change the code to. All this code is WAAAY above my head. This is MUCH more difficult to anything i've done on my raspberry pi 4B so far, as in the case of the other sensors i'm using there (Bosch BME280 based sensors), the driver code was all written and worked for me (Thanks Pimoroni!)

Here though, no such luck.

I still think there is an error with this line on my read code:

temperature, humidity = sensor.measure()

As it's complaining about this line as well as the other two in the driver file.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Roberthh » Sun Aug 14, 2022 3:15 pm

The error is raised by the function that performs the I2C communication. And that function is used by all values you request from the sensor.

Rissy
Posts: 116
Joined: Sun Aug 14, 2022 8:15 am

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Rissy » Sun Aug 14, 2022 3:19 pm

Oh right. I'll just have to believe you i'm afraid! :lol:

Are you able to help me to "modernize" this driver code so that it works with modern Python? I think it's Python 3.9 i'm running on my Pi4B and I think it's whatever version of MicroPython that this file gives me "rp2-pico-w-20220812-unstable-v1.19.1-283-g6f4d424f4.uf2"

SAYS: MicroPython v1.19.1 on 2022-08-12; Raspberry Pi Pico W with RP2040

Type "help()" for more information.

User avatar
Roberthh
Posts: 3667
Joined: Sat May 09, 2015 4:13 pm
Location: Rhineland, Europe

Re: Pico W Micro Python for SHT30 Temp/Humidity HELP!

Post by Roberthh » Sun Aug 14, 2022 4:16 pm

The SHT driver was initially made for an ESP8266 board, which does not need the ID parameter for I2C. So I changed the code a little bit, taking the creating of the I2C object out. It now reads:

Code: Select all

from machine import I2C, Pin
import time

__version__ = '0.2.3'
__author__ = 'Roberto Sánchez'
__license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"

# I2C address B 0x45 ADDR (pin 2) connected to VDD
DEFAULT_I2C_ADDRESS = 0x44


class SHT30:
    """
    SHT30 sensor driver in pure python based on I2C bus

    References:
    * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf  # NOQA
    * https://www.wemos.cc/sites/default/files/2016-11/SHT30-DIS_datasheet.pdf
    * https://github.com/wemos/WEMOS_SHT3x_Arduino_Library
    * https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/11_Sample_Codes_Software/Humidity_Sensors/Sensirion_Humidity_Sensors_SHT3x_Sample_Code_V2.pdf
    """
    POLYNOMIAL = 0x131  # P(x) = x^8 + x^5 + x^4 + 1 = 100110001

    ALERT_PENDING_MASK = 0x8000     # 15
    HEATER_MASK = 0x2000            # 13
    RH_ALERT_MASK = 0x0800          # 11
    T_ALERT_MASK = 0x0400           # 10
    RESET_MASK = 0x0010             # 4
    CMD_STATUS_MASK = 0x0002        # 1
    WRITE_STATUS_MASK = 0x0001      # 0

    # MSB = 0x2C LSB = 0x06 Repeatability = High, Clock stretching = enabled
    MEASURE_CMD = b'\x2C\x10'
    STATUS_CMD = b'\xF3\x2D'
    RESET_CMD = b'\x30\xA2'
    CLEAR_STATUS_CMD = b'\x30\x41'
    ENABLE_HEATER_CMD = b'\x30\x6D'
    DISABLE_HEATER_CMD = b'\x30\x66'

    def __init__(self, i2c=None, delta_temp=0, delta_hum=0, i2c_address=DEFAULT_I2C_ADDRESS):
        if i2c is None:
            raise ValueError('An I2C object is required.')
        self.i2c = i2c
        self.i2c_addr = i2c_address
        self.set_delta(delta_temp, delta_hum)
        time.sleep_ms(50)

    def is_present(self):
        """
        Return true if the sensor is correctly conneced, False otherwise
        """
        return self.i2c_addr in self.i2c.scan()

    def set_delta(self, delta_temp=0, delta_hum=0):
        """
        Apply a delta value on the future measurements of temperature and/or humidity
        The units are Celsius for temperature and percent for humidity (can be negative values)
        """
        self.delta_temp = delta_temp
        self.delta_hum = delta_hum

    def _check_crc(self, data):
        # calculates 8-Bit checksum with given polynomial
        crc = 0xFF

        for b in data[:-1]:
            crc ^= b
            for _ in range(8, 0, -1):
                if crc & 0x80:
                    crc = (crc << 1) ^ SHT30.POLYNOMIAL
                else:
                    crc <<= 1
        crc_to_check = data[-1]
        return crc_to_check == crc

    def send_cmd(self, cmd_request, response_size=6, read_delay_ms=100):
        """
        Send a command to the sensor and read (optionally) the response
        The responsed data is validated by CRC
        """
        try:
            self.i2c.writeto(self.i2c_addr, cmd_request)
            if not response_size:
                return
            time.sleep_ms(read_delay_ms)
            data = self.i2c.readfrom(self.i2c_addr, response_size)

            for i in range(response_size//3):
                if not self._check_crc(data[i*3:(i+1)*3]):  # pos 2 and 5 are CRC
                    raise SHT30Error(SHT30Error.CRC_ERROR)
            if data == bytearray(response_size):
                raise SHT30Error(SHT30Error.DATA_ERROR)
            return data
        except OSError as ex:
            raise SHT30Error(SHT30Error.BUS_ERROR)
        except Exception as ex:
            raise ex

    def clear_status(self):
        """
        Clear the status register
        """
        return self.send_cmd(SHT30.CLEAR_STATUS_CMD, None)

    def reset(self):
        """
        Send a soft-reset to the sensor
        """
        return self.send_cmd(SHT30.RESET_CMD, None)

    def status(self, raw=False):
        """
        Get the sensor status register.
        It returns a int value or the bytearray(3) if raw==True
        """
        data = self.send_cmd(SHT30.STATUS_CMD, 3, read_delay_ms=20)

        if raw:
            return data

        status_register = data[0] << 8 | data[1]
        return status_register

    def measure(self, raw=False):
        """
        If raw==True returns a bytearrya(6) with sensor direct measurement otherwise
        It gets the temperature (T) and humidity (RH) measurement and return them.

        The units are Celsius and percent
        """
        data = self.send_cmd(SHT30.MEASURE_CMD, 6)

        if raw:
            return data

        t_celsius = (((data[0] << 8 |  data[1]) * 175) / 0xFFFF) - 45 + self.delta_temp
        rh = (((data[3] << 8 | data[4]) * 100.0) / 0xFFFF) + self.delta_hum
        return t_celsius, rh

    def measure_int(self, raw=False):
        """
        Get the temperature (T) and humidity (RH) measurement using integers.
        If raw==True returns a bytearrya(6) with sensor direct measurement otherwise
        It returns a tuple with 4 values: T integer, T decimal, H integer, H decimal
        For instance to return T=24.0512 and RH= 34.662 This method will return
        (24, 5, 34, 66) Only 2 decimal digits are returned, .05 becomes 5
        Delta values are not applied in this method
        The units are Celsius and percent.
        """
        data = self.send_cmd(SHT30.MEASURE_CMD, 6)
        if raw:
            return data
        aux = (data[0] << 8 | data[1]) * 175
        t_int = (aux // 0xffff) - 45
        t_dec = (aux % 0xffff * 100) // 0xffff
        aux = (data[3] << 8 | data[4]) * 100
        h_int = aux // 0xffff
        h_dec = (aux % 0xffff * 100) // 0xffff
        return t_int, t_dec, h_int, h_dec


class SHT30Error(Exception):
    """
    Custom exception for errors on sensor management
    """
    BUS_ERROR = 0x01
    DATA_ERROR = 0x02
    CRC_ERROR = 0x03

    def __init__(self, error_code=None):
        self.error_code = error_code
        super().__init__(self.get_message())

    def get_message(self):
        if self.error_code == SHT30Error.BUS_ERROR:
            return "Bus error"
        elif self.error_code == SHT30Error.DATA_ERROR:
            return "Data error"
        elif self.error_code == SHT30Error.CRC_ERROR:
            return "CRC error"
        else:
            return "Unknown error"
And it has to be started slightly different with:

Code: Select all

from machine import SoftI2C, Pin
import sht30

i2c=SoftI2C(sda=Pin(4), scl=Pin(5))
sht=sht30.SHT30(i2c=i2c)

sht.measure()
I changed the I2C type to SoftI2C. There was some trouble with hard I2C, which I have to check separately.

Post Reply