Commit 6b759ebe authored by Gallacchi Mattia's avatar Gallacchi Mattia
Browse files

Add SHT30 class

parent cbf3f2c7
Loading
Loading
Loading
Loading
+9 −6
Original line number Diff line number Diff line
@@ -7,16 +7,19 @@ project(sht30 C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

list(APPEND PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR})
set(PICO_CYW43_SUPPORTED "1")
pico_register_common_scope_var(PICO_CYW43_SUPPORTED)

pico_sdk_init()

add_executable(${PROJECT_NAME} main.c)
file(GLOB SRC *.cpp)
add_executable(${PROJECT_NAME} ${SRC})

pico_enable_stdio_usb(${PROJECT_NAME} 1)
pico_enable_stdio_uart(${PROJECT_NAME} 1)

pico_add_extra_outputs(${PROJECT_NAME})

target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(${PROJECT_NAME} pico_cyw43_arch_lwip_threadsafe_background pico_stdlib hardware_i2c)

pico_add_extra_outputs(${PROJECT_NAME})
 No newline at end of file
+21 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@
// no PICO_DEFAULT_WS2812_PIN

// --- I2C ---

#ifndef PICO_DEFAULT_I2C
#define PICO_DEFAULT_I2C 0
#endif
@@ -43,6 +44,26 @@
#define PICO_DEFAULT_I2C_SCL_PIN 9
#endif

#ifndef PICO_I2C0
#define PICO_I2C0 0
#endif
#ifndef PICO_I2C0_SDA_PIN
#define PICO_I2C0_SDA_PIN 8
#endif
#ifndef PICO_I2C0_SCL_PIN
#define PICO_I2C0_SCL_PIN 9
#endif

#ifndef PICO_I2C1
#define PICO_I2C1 1
#endif
#ifndef PICO_I2C1_SDA_PIN
#define PICO_I2C1_SDA_PIN 6
#endif
#ifndef PICO_I2C1_SCL_PIN
#define PICO_I2C1_SCL_PIN 7
#endif

// --- SPI ---
#ifndef PICO_DEFAULT_SPI
#define PICO_DEFAULT_SPI 0

sht30/main.c

deleted100644 → 0
+0 −77
Original line number Diff line number Diff line
/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

// Sweep through all 7-bit I2C addresses, to see if any slaves are present on
// the I2C bus. Print out a table that looks like this:
//
// I2C Bus Scan
//   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
// 0
// 1       @
// 2
// 3             @
// 4
// 5
// 6
// 7
//
// E.g. if slave addresses 0x12 and 0x34 were acknowledged.

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"

// I2C reserves some addresses for special purposes. We exclude these from the scan.
// These are any addresses of the form 000 0xxx or 111 1xxx
bool reserved_addr(uint8_t addr) {
    return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
}

int main() {
    // Enable UART so we can print status output
    stdio_init_all();
#if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN)
#warning i2c/bus_scan example requires a board with I2C pins
    puts("Default I2C pins were not defined");
#else
    // This example will use I2C0 on the default SDA and SCL pins (GP4, GP5 on a Pico)
    i2c_init(i2c_default, 100 * 1000);
    gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C);
    gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C);
    gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN);
    gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN);
    // Make the I2C pins available to picotool
    bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C));

    printf("\nI2C Bus Scan\n");
    printf("   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F\n");

    for (int addr = 0; addr < (1 << 7); ++addr) {
        if (addr % 16 == 0) {
            printf("%02x ", addr);
        }

        // Perform a 1-byte dummy read from the probe address. If a slave
        // acknowledges this address, the function returns the number of bytes
        // transferred. If the address byte is ignored, the function returns
        // -1.

        // Skip over any reserved addresses.
        int ret;
        uint8_t rxdata;
        if (reserved_addr(addr))
            ret = PICO_ERROR_GENERIC;
        else
            ret = i2c_read_blocking(i2c_default, addr, &rxdata, 1, false);

        printf(ret < 0 ? "." : "@");
        printf(addr % 16 == 15 ? "\n" : "  ");
    }
    printf("Done.\n");
    return 0;
#endif
}

sht30/main.cpp

0 → 100644
+91 −0
Original line number Diff line number Diff line
/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

// Sweep through all 7-bit I2C addresses, to see if any slaves are present on
// the I2C bus. Print out a table that looks like this:
//
// I2C Bus Scan
//   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
// 0
// 1       @
// 2
// 3             @
// 4
// 5
// 6
// 7
//
// E.g. if slave addresses 0x12 and 0x34 were acknowledged.

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"

#include "sht30.h"

volatile bool error = false;

bool callback(struct repeating_timer *t) 
{
    SHT30 *sht = (SHT30*)t->user_data;
    SHT30::measure mes = {0, 0};
    
    if (!sht->single_measure(SHT30::HIGH, mes)) {
        error = true;
        return false;
    }
    
    printf("Temperature: %f, Humidity: %f %\n", mes.temperature, mes.humidity);
    return true;
}

int main()
{
    struct repeating_timer timer;
    SHT30::measure mes = {0, 0};

    stdio_init_all();

    printf("\n*** Init SHT30 sensor ***\n");
    SHT30 sht;

    if (!sht.is_connected()) {
        printf("Error: SHT30 is not connected\n");
        return -1;
    }

    printf("SHT30 connected\n");

    if (!sht.read_serial_number())
        goto end;

    // if (!sht.single_measure(SHT30::HIGH, mes))
    //     goto end;

    // printf("Temperature: %f, Humidity: %f %\n", mes.temperature, mes.humidity);

    // add_repeating_timer_ms(2000, callback, &sht, &timer);

    while(!error) {
        if (!sht.single_measure(SHT30::HIGH, mes))
        goto end;

        printf("Temperature: %f, Humidity: %f %\n", mes.temperature, mes.humidity);
        sleep_ms(1000);
    }

    printf("An error occurred stopping\n");

    uint16_t status_reg;
    if (sht.read_status_reg(status_reg))
        printf("Status reg: 0x%04X\n", status_reg);

    end:
        printf("Exiting\n");
        return 1;
}
 No newline at end of file

sht30/sht30.cpp

0 → 100644
+371 −0
Original line number Diff line number Diff line
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "pico/time.h"
#include "pico.h"
#include "sht30.h"


#define I2C_DEFAULT_BAUD            100*1000 // 100 kHz
#define MILLI_SEC                   1000 // us
#define I2C_DEFAULT_TIMEOUT         25 * MILLI_SEC     

// SHT30
#define SHT30_DEFAULT_ADDR          0x44

// SHT30 Commands
#define SHT30_CMD_LEN               2
#define SHT30_DATA_LEN              6
#define SHT30_READ_SERIAL_NUMBER    0x3780
#define SHT30_SOFT_RESET            0x30A2
#define SHT30_READ_STATUS_REG       0xF32D
#define SHT30_CLEAR_STATUS_REG      0x3041
#define SHT30_ENABLE_HEATER         0x306D
#define SHT30_DISABLE_HEATER        0x3066
#define SHT30_NO_SLEEP              0x303E
#define SHT30_SINGLE_STRETCH        0x2C
#define SHT30_SINGLE_NO_STRETCH     0x24                   

// SHT30 Error codes
#define SHT30_CRC_ERROR             (PICO_ERROR_INSUFFICIENT_RESOURCES - 1)

// SHT30 conversion
#define SHT30_DIVIDER               ((0x01 << 16) - 1)
#define SHT30_NEG_CELSIUS           -45.0
#define SHT30_MULTI_CELSIUS         175.0
#define SHT30_MULTI_HUMIDITY        100.0

union status_reg_u
{
    uint16_t val;

    struct {
        uint8_t write_data_crc_status : 1;
        uint8_t command_status : 1;
        uint8_t : 2; // Reserved
        uint8_t reset_detected : 1;
        uint8_t : 3; // Reserved
        uint8_t : 2; // Reserved
        uint8_t t_tracking_alert : 1;
        uint8_t rh_tracking_alert : 1;
        uint8_t : 1; // Reserved
        uint8_t heater_status : 1;
        uint8_t : 1; // Reserved
        uint8_t alert_status : 1;
    };
};

// Single measure clock stretching enabled
const uint8_t single_stretch [][2] = {
    {SHT30_SINGLE_STRETCH, 0x10}, // Low 
    {SHT30_SINGLE_STRETCH, 0x0D}, // Medium
    {SHT30_SINGLE_STRETCH, 0x06}  // High
};

// Single measure clock stretching disabled
const uint8_t single_no_stretch [][2] = {
    {SHT30_SINGLE_NO_STRETCH, 0x16}, // Low
    {SHT30_SINGLE_NO_STRETCH, 0X0B}, // Medium
    {SHT30_SINGLE_NO_STRETCH, 0x00}, // High
};

const uint8_t crc8x_table[] = {
    0x00,0x31,0x62,0x53,0xC4,0xF5,0xA6,0x97,0xB9,0x88,0xDB,0xEA,0x7D,0x4C,0x1F,0x2E,
    0x43,0x72,0x21,0x10,0x87,0xB6,0xE5,0xD4,0xFA,0xCB,0x98,0xA9,0x3E,0x0F,0x5C,0x6D,
    0x86,0xB7,0xE4,0xD5,0x42,0x73,0x20,0x11,0x3F,0x0E,0x5D,0x6C,0xFB,0xCA,0x99,0xA8,
    0xC5,0xF4,0xA7,0x96,0x01,0x30,0x63,0x52,0x7C,0x4D,0x1E,0x2F,0xB8,0x89,0xDA,0xEB,
    0x3D,0x0C,0x5F,0x6E,0xF9,0xC8,0x9B,0xAA,0x84,0xB5,0xE6,0xD7,0x40,0x71,0x22,0x13,
    0x7E,0x4F,0x1C,0x2D,0xBA,0x8B,0xD8,0xE9,0xC7,0xF6,0xA5,0x94,0x03,0x32,0x61,0x50,
    0xBB,0x8A,0xD9,0xE8,0x7F,0x4E,0x1D,0x2C,0x02,0x33,0x60,0x51,0xC6,0xF7,0xA4,0x95,
    0xF8,0xC9,0x9A,0xAB,0x3C,0x0D,0x5E,0x6F,0x41,0x70,0x23,0x12,0x85,0xB4,0xE7,0xD6,
    0x7A,0x4B,0x18,0x29,0xBE,0x8F,0xDC,0xED,0xC3,0xF2,0xA1,0x90,0x07,0x36,0x65,0x54,
    0x39,0x08,0x5B,0x6A,0xFD,0xCC,0x9F,0xAE,0x80,0xB1,0xE2,0xD3,0x44,0x75,0x26,0x17,
    0xFC,0xCD,0x9E,0xAF,0x38,0x09,0x5A,0x6B,0x45,0x74,0x27,0x16,0x81,0xB0,0xE3,0xD2,
    0xBF,0x8E,0xDD,0xEC,0x7B,0x4A,0x19,0x28,0x06,0x37,0x64,0x55,0xC2,0xF3,0xA0,0x91,
    0x47,0x76,0x25,0x14,0x83,0xB2,0xE1,0xD0,0xFE,0xCF,0x9C,0xAD,0x3A,0x0B,0x58,0x69,
    0x04,0x35,0x66,0x57,0xC0,0xF1,0xA2,0x93,0xBD,0x8C,0xDF,0xEE,0x79,0x48,0x1B,0x2A,
    0xC1,0xF0,0xA3,0x92,0x05,0x34,0x67,0x56,0x78,0x49,0x1A,0x2B,0xBC,0x8D,0xDE,0xEF,
    0x82,0xB3,0xE0,0xD1,0x46,0x77,0x24,0x15,0x3B,0x0A,0x59,0x68,0xFF,0xCE,0x9D,0xAC
};

void print_status_reg(status_reg_u status)
{
    printf("\nStatus reg value: 0x%04X\n", status.val);

    printf("Alert: %d\n", status.alert_status);
    printf("Heater: %d\n", status.heater_status);
    printf("RH tracking: %d\n", status.rh_tracking_alert);
    printf("T tracking: %d\n", status.t_tracking_alert);
    printf("Reset: %d\n", status.reset_detected);
    printf("CMD status: %d\n", status.command_status);
    printf("CRC status: %d\n\n", status.write_data_crc_status);
}

inline uint8_t calculate_crc8x(uint8_t *data, size_t len)
{
    uint8_t crc = 0xFF;

    for (size_t i = 0; i < len; i++) {
        crc = crc8x_table[data[i] ^ crc];
    }

    return crc;
}

int check_crc(uint8_t *data, size_t data_len)
{   
    size_t len = data_len - 1;
    uint8_t crc = data[len];
    return calculate_crc8x(data, len) == crc ? 0 : SHT30_CRC_ERROR;
}

const char* which_error(int error_code)
{
    switch (error_code)
    {
    case PICO_ERROR_GENERIC:
        return "Address not acknowledged or device not present";
    
    case PICO_ERROR_TIMEOUT:
        return "Timeout reached";

    case SHT30_CRC_ERROR:
        return "CRC don't match";

    default:
        return "OK";
    }
}

float convert_temperature_celsius(uint16_t temp)
{
    float temperature = SHT30_NEG_CELSIUS + (SHT30_MULTI_CELSIUS * (float)temp) / SHT30_DIVIDER;

    return temperature;
}

float convert_humidity(uint16_t hum)
{
    float humidity = SHT30_MULTI_HUMIDITY * ((float) hum) / SHT30_DIVIDER;

    return humidity;
}

SHT30::SHT30(unsigned int i2c_channel) : i2c_channel_(i2c_channel), connected_(false)
{
    status_reg_u status;

    switch(i2c_channel_) {
        case 0:
            i2c_inst_ = i2c0;
            i2c_scl_ = PICO_I2C0_SCL_PIN;
            i2c_sda_ = PICO_I2C0_SDA_PIN;
            break;
        case 1:
            i2c_inst_ = i2c1;
            i2c_scl_ = PICO_I2C1_SCL_PIN;
            i2c_sda_ = PICO_I2C1_SDA_PIN;
            break;
        default:
            printf("Error: I2C channel %d not supported\n", i2c_channel_);
            return;
    }

    i2c_init(i2c_inst_, I2C_DEFAULT_BAUD);
    gpio_set_function(i2c_sda_, GPIO_FUNC_I2C);
    gpio_set_function(i2c_scl_, GPIO_FUNC_I2C);
    gpio_pull_up(i2c_scl_);
    gpio_pull_up(i2c_sda_);

    // Make the I2C pins available to picotool
    bi_decl(bi_2pins_with_func(i2c_sda_, i2c_scl_, GPIO_FUNC_I2C));

    if (!reset())
        return;

    if (!write_command(SHT30_NO_SLEEP))
        return;   
    
    if (read_status_reg(status.val)) {
        if (status.val == 0x8010)
            connected_ = true;
        else
            print_status_reg(status);
    } 
}

bool SHT30::read_serial_number()
{
    int ret = 0;
    uint16_t serial_number = 0;

    if (!write_command(SHT30_READ_SERIAL_NUMBER)) {
        goto error;
    }

    if (!read_response(&serial_number, 1))
        goto error;


    printf("SHT30 serial number: 0x%04X\n", serial_number);

    return true;

    error:
        printf("Error: Failed to read serial number\n");
        return false;
}

bool SHT30::reset()
{
    if(!write_command(SHT30_SOFT_RESET)) {
        printf("Error: Failed to reset SHT30 device\n");
        return false;
    }

    return true;
}

bool SHT30::is_connected()
{
    return connected_;
}

bool SHT30::write_command(uint8_t *cmd, size_t len)
{
    int ret = 0;

    if ((ret = i2c_write_blocking(i2c_inst_, SHT30_DEFAULT_ADDR, cmd, len, false)) != len) {
        printf("Error [%d]: I2C write command 0x", ret);
        for (size_t i = 0; i < len; i++) {
            printf("%02X", cmd[i]);
        }
        printf(" failed. %s\n", which_error(ret));
        return false;
    }

    // Since the device needs approx 1 ms before a new command can be sent we sleep.
    sleep_ms(1);
    return true;
}

bool SHT30::write_command(uint16_t cmd)
{
    uint8_t tx_data[] = { (uint8_t)(cmd >> 8), (uint8_t)(cmd & 0xFF) };
    return write_command(tx_data, SHT30_CMD_LEN);
}

bool SHT30::read_response(uint16_t *buf, size_t len)
{
    int ret = 0;
    size_t rx_data_len = 2 * len + len;
    uint8_t rx_data[rx_data_len] = {0};

    // Read 3 bytes at the time and check the CRC
    if ((ret = i2c_read_blocking(i2c_inst_, SHT30_DEFAULT_ADDR, rx_data, rx_data_len, false)) != rx_data_len) {
        goto error;
    }

    for (size_t i = 0; i < len; i++) {
        if ((ret = check_crc(&rx_data[i * 3], 3)) < 0) {
            goto error;
        }
        buf[i] = (rx_data[i * 3] << 8) | rx_data[i * 3 + 1];
    }

    if ((ret = i2c_get_read_available(i2c_inst_)) != 0)
        printf("Warning at least %d more bytes are available\n", ret);

    return true;

    error:
        printf("Error [%d]: Failed to read. %s\n", ret, which_error(ret));
        return false;
}

bool SHT30::read_status_reg(uint16_t &reg)
{
    int ret = 0;
    uint16_t buf;

    if (!write_command(SHT30_READ_STATUS_REG))
        goto error;

    if (!read_response(&buf, 1))
        goto error;

    reg = buf;

    return true;

    error:
        printf("Failed to read status register\n");
        return false;
}

bool SHT30::heater_enable()
{
    status_reg_u status;

    if (!write_command(SHT30_ENABLE_HEATER)) return false;

    if (!read_status_reg(status.val)) return false;

    if (!status.heater_status) {
        printf("Error: Failed to enable heater\n");
        print_status_reg(status);
        return false;
    }

    return true;
}

bool SHT30::heater_disable()
{
    status_reg_u status;

    if (!write_command(SHT30_DISABLE_HEATER)) return false;

    if (!read_status_reg(status.val)) return false;

    if (status.heater_status) {
        printf("Error: Failed to disable heater\n");
        print_status_reg(status);
        return false;
    }

    return true;
}

bool SHT30::single_measure(Repeatability repeatability, struct SHT30::measure &measure, bool clock_stretching)
{
    uint8_t cmd[SHT30_CMD_LEN] = {0};
    uint16_t buf[2] = {0};
    int ret = 0;

    if (clock_stretching)
        memcpy(cmd, single_stretch[repeatability], 2);
    else
        memcpy(cmd, single_no_stretch[repeatability], 2);

    if(!write_command(cmd, SHT30_CMD_LEN))
        goto error;

    if (!read_response(buf, sizeof(buf)))
        goto error;

    measure.temperature = convert_temperature_celsius(buf[0]);
    measure.humidity = convert_humidity(buf[1]);

    return true;

    error:
        printf("Failed to get measure\n");
        return false;
}

SHT30::~SHT30()
{
    i2c_deinit(i2c_inst_);
}
 No newline at end of file
Loading