STM32CUBEMX Analog IIC bus reading SHT40 temperature and humidity sensor

1: Use STM32CUBEMX to configure the IIC analog bus

#define SHT30_SCL_PORT GPIOC

#define SHT30_SCL_PIN GPIO_PIN_6 // GPIO_MODE_OUTPUT_PP

#define SHT30_SDA_PORT GPIOB

#define SHT30_SDA_PIN GPIO_PIN_15 // GPIO_MODE_OUTPUT_OD

The SDA data pin is configured as OD output, and the CLK pin is configured as PP output.

2: SHT40 command table

The command to start reading temperature and humidity is 0xFD.

3: Here is the code, don’t read it, just use it directly. It is modified from sht30. The macro definition still uses the part of SHT30. There is only one difference. See the comments in the SHT30.c file.

sht30.h file
#ifndef __SHT30_H__
#define __SHT30_H__

#include <stdint.h>
#include "main.h"
#include "stm32f1xx_hal.h"

#define I2C_WRITE (0)
#define I2C_READ (1)

#define SHT30_INQUIRE_CNT (500)
#define SHT30_ADDR (0x44)

#define SHT30_SCL_PORT GPIOC
#define SHT30_SCL_PIN GPIO_PIN_6 // GPIO_MODE_OUTPUT_PP
#define SHT30_SDA_PORT GPIOB
#define SHT30_SDA_PIN GPIO_PIN_15 // GPIO_MODE_OUTPUT_OD

void i2c_init(void);
int read_sht30_data(float *temp, float *humi);

#endif /* sht30.h */


sht30.c file

/**
 * @brief SHT30 temperature and humidity sensor related, use analog IIC to read data
 */

#include <stdint.h>
#include "sht30.h"
#define SHT40
//#define SHT30
/**
 * @brief crc8 check function, the polynomial is x^8 + x^5 + x^4 + 1
 * @param data The data to be verified
 * @param len The number of bytes of data to be verified
 * @retval verification result
 * @note This verification is suitable for data verification of SHT3 temperature and humidity sensor
 */
static uint8_t Crc8(uint8_t *data, int len)
{
    const uint8_t POLYNOMIAL = 0x31;
    uint8_t crc = 0xFF;
    int i, j;

    for (i = 0; i < len; + + i)
    {
        crc ^= *data + + ;

        for (j = 0; j < 8; + + j)
        {
            crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
        }
    }

    return crc;
}

/**
 * @brief i2c’s delay function, the delay time must be > 4us
 * @param none
 * @retval None
 * @note A logic analyzer can be used to measure the frequency working conditions during I2C communication.
 */
static void i2c_delay(void)
{
    int i;

    for (i = 0; i < 10; i + + )
    {
        asm("nop"); // Empty statement
    }
}

/**
 * @brief i2c SCL pull high
 * @param None
 * @retval None
 */
static void i2c_scl_high(void)
{
    HAL_GPIO_WritePin(SHT30_SCL_PORT, SHT30_SCL_PIN, GPIO_PIN_SET);
}

/**
 * @brief i2c SCL pulled low
 * @param None
 * @retval None
 */
static void i2c_scl_low(void)
{
    HAL_GPIO_WritePin(SHT30_SCL_PORT, SHT30_SCL_PIN, GPIO_PIN_RESET);
}

/**
 * @brief i2c SDA pull high
 * @param None
 * @retval None
 */
static void i2c_sda_high(void)
{
    HAL_GPIO_WritePin(SHT30_SDA_PORT, SHT30_SDA_PIN, GPIO_PIN_SET);
}

/**
 * @brief i2c SDA pull low
 * @param None
 * @retval None
 */
static void i2c_sda_low(void)
{
    HAL_GPIO_WritePin(SHT30_SDA_PORT, SHT30_SDA_PIN, GPIO_PIN_RESET);
}

/**
 * @brief Read the value on the SDA line
 * @param None
 * @retval The value read, 0 or 1
 */
static uint8_t i2c_read_sda_value(void)
{
    return HAL_GPIO_ReadPin(SHT30_SDA_PORT, SHT30_SDA_PIN);
    ;
}

/**
 * @brief i2c start signal
 * @param None
 * @retval None
 */
static void i2c_start(void)
{
    /* When SCL is high, a falling edge on SDA indicates the I2C bus start signal */
    i2c_scl_high();
    i2c_sda_high();
    i2c_delay();

    i2c_sda_low();
    i2c_delay();

    i2c_scl_low();
    i2c_delay();
}

/**
 * @brief i2c stop signal
 * @param none
 * @retval None
 */
static void i2c_stop(void)
{
    /* When SCL is high, a rising edge appears on SDA to indicate the I2C bus stop signal */
    i2c_sda_low();
    i2c_scl_high();
    i2c_delay();

    i2c_sda_high();
}

/**
 * @brief i2c sends a byte
 * @param The data to be sent
 * @retval None
 */
void i2c_write_byte(uint8_t data)
{
    uint8_t i;

    i2c_scl_low();
    i2c_delay();

    /* Send the high bit bit7 of the byte first */
    for (i = 0; i < 8; i + + )
    {
        if (data & 0x80)
        {
            i2c_sda_high();
        }
        else
        {
            i2c_sda_low();
        }
        i2c_delay();

        i2c_scl_high();
        i2c_delay();
        i2c_scl_low();

        if(i==7)
        {
            i2c_sda_high(); // Release the bus
        }
        data <<= 1; // Shift one bit to the left
        i2c_delay();
    }
}

/**
 * @brief i2c reads a byte
 * @param None
 * @retval None
 */
uint8_t i2c_read_byte(void)
{
    uint8_t i;
    uint8_t value;

    /* Read that the first bit is bit7 of data */
    value = 0;
    for (i = 0; i < 8; i + + )
    {
        value <<= 1;
        i2c_scl_high();
        i2c_delay();
        if (i2c_read_sda_value() != 0)
        {
            value + + ;
        }

        i2c_scl_low();
        i2c_delay();
    }

    return value;
}

/**
 * @brief CPU waits for the response signal from the slave device
 * @param none
 * @retval 0 means correct response, 1 means no response
 */
static uint8_t i2c_wait_ack(void)
{
    uint8_t ret;

    i2c_sda_high(); /* CPU releases SDA bus */
    i2c_delay();

    i2c_scl_high(); /* CPU driver SCL = 1, at this time the device will return ACK response */
    i2c_delay();
    if (i2c_read_sda_value() == 1) /* CPU reads SDA port line status */
    {
        ret = 1;
    }
    else
    {
        ret = 0;
    }

    i2c_scl_low();
    i2c_delay();

    return ret;
}

/**
 * @brief CPU generates an ACK signal
 * @param none
 * @retval None
 */
static void i2c_ack(void)
{
    i2c_sda_low(); /* CPU driver SDA = 0 */
    i2c_delay();

    i2c_scl_high(); /* CPU generates 1 clock */
    i2c_delay();

    i2c_scl_low();
    i2c_delay();

    i2c_sda_high(); /* CPU releases SDA bus */
}

/**
 * @brief CPU generates a NACK signal
 * @param None
 * @retval None
 */
static void i2c_Nack(void)
{
    i2c_sda_high(); /* CPU driver SDA = 1 */
    i2c_delay();

    i2c_scl_high(); /* CPU generates 1 clock */
    i2c_delay();

    i2c_scl_low();
    i2c_delay();
}

/**
 * @brief i2c sends multiple bytes
 * @param The address of the slave to send
 * @param pointer to the data to be written
 * @param The length of the data to be written
 * @retval Returns 0 on success, -1 on failure
 */
int i2c_write_bytes(uint8_t addr, uint8_t *write_buff, uint8_t buff_size)
{
    uint16_t i, j;

    // 1. Send a stop signal
    i2c_stop();

    /* Determine whether the internal write operation is completed by checking the device response, usually less than 10ms
    When the CLK frequency is 200KHz, the number of queries is about 30 times
    */
    for (i = 0; i < SHT30_INQUIRE_CNT; i + + )
    {
        // 2. Initiate the I2C bus start signal
        i2c_start();

        // 3. Initiate the control byte, the upper 7 bits are the address, bit0 is the read and write control bit, 0 means write, 1 means read
        i2c_write_byte(addr << 1 | I2C_WRITE);

        // 4. Send a clock to determine whether the device responds correctly
        if (i2c_wait_ack() == 0)
        {
            break;
        }
    }
    if (i == SHT30_INQUIRE_CNT)
    {
        goto cmd_fail; // write timeout
    }

    for (i = 0; i < buff_size; + + i)
    {
        for (j = 0; j < SHT30_INQUIRE_CNT; j + + )
        {
            // 5. Send data
            i2c_write_byte(write_buff[i]);

            // 6. Wait for ACK
            if (i2c_wait_ack() == 0)
            {
                break;
            }
        }

        if (j == SHT30_INQUIRE_CNT)
        {
            goto cmd_fail; /* No response from slave device */
        }
    }

    // 7. Send stop signal
    i2c_stop();

    return 0;

cmd_fail:
    i2c_stop(); // Send write timeout
    return -1;
}

/**
 * @brief i2c sends multiple bytes
 * @param The address of the slave to send
 * @param pointer to the data to be read
 * @param The length of the data to be read
 * @retval Returns 0 on success, -1 on failure
 */
int i2c_read_bytes(uint8_t addr, uint8_t *read_buff, uint8_t buff_size)
{
    uint16_t i;

    for (i = 0; i < SHT30_INQUIRE_CNT; i + + )
    {
        /* 1. Initiate the I2C bus start signal */
        i2c_start();

        /* 2. If reading, first write the slave address */
        i2c_write_byte(addr << 1 | I2C_WRITE);

        /* 3. Send a clock to determine whether the device responds correctly */
        if (i2c_wait_ack() == 0)
        {
            break;
        }
    }
    if (i == SHT30_INQUIRE_CNT)
    {
        goto cmd_fail; // write timeout
    }

    for (i = 0; i < SHT30_INQUIRE_CNT; i + + )
    {
        /* 4. Restart the I2C bus. The purpose of the previous code is to transfer the address, and the following starts to read the data */
        i2c_start();

        /* 5. Send read control */
        i2c_write_byte(addr << 1 | I2C_READ);

        /* 6. Send a clock to determine whether the device responds correctly */
        if (i2c_wait_ack() == 0)
        {
            break;
        }
    }
    if (i == SHT30_INQUIRE_CNT)
    {
        goto cmd_fail; // write timeout
    }

    /* 7. Loop to read data */
    for (i = 0; i < buff_size; i + + )
    {
        read_buff[i] = i2c_read_byte(); /* Read 1 byte */

        /* After each byte is read, an Ack needs to be sent. The last byte does not require an Ack and a Nack is sent */
        if (i != buff_size - 1)
        {
            i2c_ack(); // After the middle byte is read, the CPU generates an ACK signal (driver SDA = 0)
        }
        else
        {
            i2c_Nack(); // After the last byte is read, the CPU generates a NACK signal (driver SDA = 1)
        }
    }

    /* 8. Send I2C bus stop signal */
    i2c_stop();

    return 0; /* execution successful */

cmd_fail:
    i2c_stop(); // Send write timeout
    return -1;
}

/**
 * @brief I2C initialization
 * @param none
 * @retval None
 */
void i2c_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = SHT30_SDA_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(SHT30_SDA_PORT, &GPIO_InitStruct);
    i2c_stop();
}

/**
 * @brief Read the value of sht30 temperature and humidity sensor
 * @param pointer to the stored temperature value
 * @param pointer to the stored humidity value
 * @retval Returns 0 on success, -1 on failure
 */
int read_sht30_data(float *temp, float *humi)
{
#ifdef SHT40
    uint8_t writeData[2] = {0xFD,0x06};//{0x2C, 0x06};//SHT40 sends 0xFD, SHT30 sends 0x2C 0x06
#endif
#ifdef SHT30
   uint8_t writeData[2] = {0x2C, 0x06};//SHT40 sends 0xFD, SHT30 sends 0x2C 0x06
#endif
    uint8_t readData[6] = {0};
    uint8_t retryCount =0;
    do
    {
#ifdef SHT40
        if (i2c_write_bytes(SHT30_ADDR, writeData, 1) == -1)
        {
            return -1;
        }
#endif
#ifdef SHT30
        if (i2c_write_bytes(SHT30_ADDR, writeData, 2) == -1)
        {
            return -1;
        }
#endif
        HAL_Delay(20);
        if (i2c_read_bytes(SHT30_ADDR, readData, 6) == -1)
        {
            return -1;
        }
        retryCount + + ;
        if(retryCount>10)
        {
          return -1;
        }
    } while (Crc8( & amp;readData[0], 2) != readData[2] || Crc8( & amp;readData[3], 2) != readData[5]);

    *temp = (1.0 * 175 * (readData[0] * 256 + readData[1])) / 65535.0 - 45;

#ifdef SHT40
    *humi = (1.0 * 125 * (readData[3] * 256 + readData[4])) / 65535.0 - 6.0;
#endif
#ifdef SHT30
    *humi = (1.0 * 100 * (readData[3] * 256 + readData[4])) / 65535.0;
#endif
    return 0;
}

4. Initialize and call the program.

void ReadSHT30()
{
  i2c_init();
  read_sht30_data( & amp;temperature, & amp;humidity);
  zhanshiban_Equip_sensor.Humidity = humidity + zhanshibanPar.Humidity_Check - 3.9;
  if (zhanshiban_Equip_sensor.Humidity < 0)
  {
    zhanshiban_Equip_sensor.Humidity = 0.0;
  }
  zhanshiban_Equip_sensor.Temerature = temperature + zhanshibanPar.Temperature_Check;
}

5. This ends.

Did you read the temperature and humidity values?