Skip to content

Overview ​

The SHT30 is a temperature and humidity sensor in the SHT3x series, far more accurate than the entry-level DHT11: temperature accuracy ±0.2°C, humidity accuracy ±2%RH. It's read over the I2C bus (a two-wire serial communication — one clock SCL, one data SDA), and the data carries CRC checking (cyclic redundancy check, an algorithm that detects whether data was corrupted in transit — like the anti-counterfeit digit at the end of a waybill number). This tutorial uses the Ai-WB2 to read the SHT30's temperature and humidity, printing them on the serial in real time, and walks through the full flow of wiring → coding → building → flashing (writing the compiled program into the board's chip) → running and verification.

In plain words: the SHT30 is a "refined" thermometer-hygrometer. The main controller shouts its "door number" (I2C address 0x44) through the I2C hallway (two wires), sends a "start measuring" command (0x2400), and it replies with 6 bytes: 2 bytes temperature + 1 anti-counterfeit byte, 2 bytes humidity + 1 anti-counterfeit byte. The controller verifies the anti-counterfeit code (CRC) first, then converts to temperature and humidity — if the code doesn't match, the data was corrupted in transit, so it prints N/A rather than handing you a wrong number.

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/iot-solution/demo_sht3x; the code can be found directly in the local SDK.

🎯Page GoalRead the SHT30 temperature and humidity over I2C, parse the data with CRC8 checking, and print temperature and humidity on the serial every second.
🧰Prerequisites① An Ai-WB2 development board + an SHT30 temperature and humidity sensor module ② Development environment set up per [SDK Installation](../sdk/sdk_intro), and [GPIO Output (LED)](../basic/gpio_led) done.
🔗RelatedI2C fundamentals: [I2C Protocol](../basic/i2c) (the code is basically the same as this example); the entry-level temperature/humidity sensor: [DHT11 Temperature and Humidity Sensor](./dht11).

①
Hardware Wiring

Wire per the official example (see the SDK’s applications/iot-solution/demo_sht3x/README.md), using dupont wires (jumper wires with pins at both ends):

Ai-WB2 Pin SHT30 Pin
IO12 SCL
IO3 SDA
3V3 VCC
GND GND

💡 The SHT30 is an I2C device — VCC to 3.3V (the SHT30 accepts 2.4V~5.5V; this tutorial uses 3.3V throughout). 💡 I2C’s SCL/SDA are open-drain signals (a pin can only actively pull low, not high), so pull-up resistors are required; most SHT30 modules have onboard pull-ups; for a bare chip, add a 4.7kΩ pull-up on each of SCL and SDA to 3V3.

②
Enter the Example Project

This tutorial directly uses the demo_sht3x example project shipped with the official SDK; open a terminal and enter it:

cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_sht3x

Note: cd is the “change directory” command — entering the SHT3x example project directory; all subsequent make build and make flash flash commands must run in this directory first.

Project structure:

File Purpose
demo_sht3x/main.c Main program source, the main file this tutorial looks at
Makefile Build entry, usually no changes needed
proj_config.mk Project config (flash size, feature switches, etc.), usually no changes needed
③
Write the Code

Open demo_sht3x/main.c — the complete code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/iot-solution/demo_sht3x/demo_sht3x/main.c).

Code highlights:

Code Purpose
.scl = 12、.sda = 3、.freq = 100000 I2C pins IO12/IO3, clock 100kHz; wrong pins = no communication
SHT31_DEFAULT_ADDR 0x0044 The SHT30’s I2C address (door number): ADDR pin floating/low is 0x44, tied high is 0x45; wrong address = sensor not found
SHT31_MEAS_HIGHREP 0x2400 High-repeatability measurement command (best accuracy, ~15ms measurement); without the command the sensor doesn’t measure
hosal_i2c_master_send(...) / hosal_i2c_master_recv(...) First send the 2-byte command, then receive 6 bytes (temperature 2B+CRC, humidity 2B+CRC); no receive = no temperature/humidity
crc8(&data.st_high, 2) == data.st_crc8 CRC8 checking (polynomial 0x31) against transmission errors; prints N/A when it fails — never trusts bad data
Conversion formulas temperature = raw×17500÷0xFFFF−4500 (0.01°C), humidity = raw×10000÷0xFFFF (0.01%RH)
④
Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program file) the board can run; -j8 builds with 8 parallel CPU cores, faster.

On success a firmware build_out/demo_sht3x.bin is generated.

⚠️ If it reports riscv64-unknown-elf-gcc: command not found, the toolchain permissions aren’t configured — run cd toolchain/riscv/Linux && . chmod755.sh first, then rebuild.

⑤
Flash the Firmware

Keep the board connected via USB, confirm the serial device (usually /dev/ttyUSB0 on Linux), and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board’s chip. After p= comes the serial device (change it to your computer’s actual one — check with ls /dev/ttyUSB*), b= is the flash baud rate (transfer speed).

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode (some boards enter automatically); wait for the progress bar to complete — that means the flash succeeded. For flashing on Windows, see Windows Quick Start.

⑥
Run and Verify

After flashing, the board automatically restarts and runs; open the serial assistant (baud rate 921600 — the baud rate is the serial transfer speed, both ends must be set the same) and temperature/humidity prints every 1 second:

temperature: 26.54 C	humidity: 45 %
temperature: 26.53 C	humidity: 45 %
...

Breathe on the sensor — the humidity value should rise noticeably; pinch the sensor with your hand — the temperature value should slowly climb. On CRC failure the serial prints N/A.

Seeing real-time temperature/humidity print every 1 second means success; if it keeps printing N/A or the values are fixed, it hasn’t succeeded yet — check the FAQ at the end.

💡 Watch the IO12/IO3 waveforms with a logic analyzer to see the I2C START, address, data and STOP timing (see the official example’s img/logic_analyzer.jpg).


API Summary for This Tutorial ​

hosal_i2c_init(i2c) ​

Configures the I2C controller per the pins, frequency and master/slave mode in the dev struct (this tutorial initializes master mode to read the SHT30).

Parameters:

  • i2c: hosal_i2c_dev_t struct pointer, required. Key fields: config.mode (HOSAL_I2C_MODE_MASTER master / HOSAL_I2C_MODE_SLAVE slave), config.scl/config.sda (SCL/SDA pin numbers, this tutorial IO12/IO3), config.freq (100000 standard 100kHz / 400000 fast 400kHz), config.address_width (HOSAL_I2C_ADDRESS_WIDTH_7BIT / HOSAL_I2C_ADDRESS_WIDTH_10BIT)

Return: 0 on success; negative error code on failure

hosal_i2c_master_send(i2c, dev_addr, data, size, timeout) ​

As the master, sends a frame of data to the slave at the given address (including start/address/stop bits; this tutorial sends the SHT30 measurement command).

Parameters:

  • i2c: hosal_i2c_dev_t struct pointer
  • dev_addr: slave device address (the byte after shifting the 7-bit address left by 1), e.g. 0x44
  • data: send data buffer pointer, required
  • size: bytes to send, values: 1~256
  • timeout: wait timeout (ms), values: e.g. 100

Return: 0 on success; negative error code on failure (no ACK/timeout)

hosal_i2c_master_recv(i2c, dev_addr, data, size, timeout) ​

As the master, reads a frame of data from the given slave (this tutorial receives the SHT30's 6-byte temperature/humidity data).

Parameters:

  • i2c: hosal_i2c_dev_t struct pointer
  • dev_addr: slave device address (same rule as master_send)
  • data: receive buffer pointer, required
  • size: expected bytes to receive (this tutorial 6)
  • timeout: wait timeout (ms)

Return: 0 on success; negative error code on failure

crc8(data, len) ​

Computes the data checksum per the SHT3x datasheet's CRC8 algorithm (polynomial 0x31, initial value 0xFF), used to compare against the received checksum byte.

Parameters:

  • data: data pointer to check (e.g. the 2-byte raw temperature), required
  • len: bytes to check, values: any positive integer (this tutorial 2)

Return: 8-bit checksum (uint8_t). In this tutorial, if it equals data.st_crc8, the data is trustworthy. This function is a utility implemented by this example itself — see demo_sht3x/main.c

blog_info(fmt, ...) ​

Prints an INFO-level log (UART0, subject to level filtering); this tutorial prints temperature/humidity with it.

Parameters:

  • fmt: format string, same usage as printf, required
  • ...: variadic args matching the fmt placeholders; can be omitted

Return: none

vTaskDelay(ms) ​

Suspends the current task for the given milliseconds, yielding the CPU to other tasks.

Parameters:

  • ms: delay in milliseconds, values: any non-negative integer (internally converted to system ticks via pdMS_TO_TICKS)

Return: none


Full Code ​

Below is the complete demo_sht3x/main.c source, identical to the official example (applications/iot-solution/demo_sht3x/demo_sht3x/main.c):

📜 Click to expand the full demo_sht3x/main.c code
c
#include <stdio.h>

#include <FreeRTOS.h>
#include <task.h>

#include <hosal_i2c.h>
#include <bl_gpio.h>
#include <blog.h>

#define SHT31_DEFAULT_ADDR 0x0044
#define SHT31_MEAS_HIGHREP 0x2400

#pragma pack(1)
struct sht3x_data
{
    uint8_t st_high;
    uint8_t st_low;
    uint8_t st_crc8;
    uint8_t srh_high;
    uint8_t srh_low;
    uint8_t srh_crc8;
};
#pragma pack()

static uint8_t crc8(uint8_t *data, int len)
{
    const uint8_t POLYNOMIAL = 0x31;
    uint8_t crc = 0xFF;
    for (int j = len; j; --j)
    {
        crc ^= *data++;
        for (int i = 8; i; --i)
        {
            crc = (crc & 0x80)
                      ? (crc << 1) ^ POLYNOMIAL
                      : (crc << 1);
        }
    }
    return crc;
}

int main(void)
{
    static hosal_i2c_dev_t i2c0 = {
        .config = {
            .address_width = HOSAL_I2C_ADDRESS_WIDTH_7BIT,
            .freq = 100000,
            .mode = HOSAL_I2C_MODE_MASTER,
            .scl = 12,
            .sda = 3,
        },
        .port = 0,
    };

    hosal_i2c_init(&i2c0);

    for (;;) {
        
        struct sht3x_data data;

        uint8_t command[2] = { SHT31_MEAS_HIGHREP >> 8, SHT31_MEAS_HIGHREP & 0xff };
        hosal_i2c_master_send(&i2c0, SHT31_DEFAULT_ADDR, command, sizeof command, 100);
        hosal_i2c_master_recv(&i2c0, SHT31_DEFAULT_ADDR, (uint8_t*)&data, sizeof data, 100);

        char temperature_str[8];
        char humidity_str[8];

        if (crc8(&data.st_high, 2) == data.st_crc8) {
            uint16_t st = data.st_high;
            st <<= 8;
            st |= data.st_low;

            int temp = st;
            temp *= 17500;
            temp /= 0xffff;
            temp = -4500 + temp;

            int temperature_integer = temp / 100;
            
            if (temp < 0) {
                temp = -temp;
            }

            unsigned temperature_decimal = temp % 100;

            sprintf(temperature_str, "%d.%02u C", temperature_integer, temperature_decimal);
        }
        else {
            sprintf(temperature_str, "%s", "N/A C");
        }
        
        if (crc8(&data.srh_high, 2) == data.srh_crc8) {
            uint16_t srh = data.srh_high;
            srh <<= 8;
            srh |= data.srh_low;

            unsigned humidity = srh;
            humidity *= 10000;
            humidity /= 0xFFFF;

            unsigned humidity_integer = humidity / 100;

            sprintf(humidity_str, "%u %%", humidity_integer);
        }
        else {
            sprintf(humidity_str, "N/A %%");
        }

        blog_info("temperature: %s\thumidity: %s\r\n", temperature_str, humidity_str);

        vTaskDelay(portTICK_RATE_MS * 1000);
    }

    return 0;
}

FAQ & Troubleshooting ​

⚠️ Prints N/A (CRC check failed)
Cause: poor wiring contact, no pull-up resistors on SDA/SCL, or insufficient module power
Fix: confirm 4.7kΩ pull-ups on SDA/SCL; shorten the dupont wires; confirm the module's VCC to 3V3 and common ground (GND to GND)

⚠️ Data is 0 or fixed
Cause: wrong slave address (the SHT30's address becomes 0x45 when the ADDR pin is tied to 3V3)
Fix: confirm the module's ADDR state; change SHT31_DEFAULT_ADDR to 0x0045 or the matching address, then rebuild and reflash

⚠️ No data at all (nothing prints)
Cause: wrong wiring, damaged sensor, or IO12/IO3 occupied by another multiplexed function
Fix: check the wiring table one by one; confirm no other module in the project uses these two pins; try another SHT30

⚠️ Serial device not found or no permission
Cause: on Linux /dev/ttyUSB0 doesn't exist or permission denied; on Windows the USB-to-serial driver isn't installed
Fix: on Linux check with ls /dev/ttyUSB*; if permission denied run sudo usermod -aG dialout $USER and log back in; on Windows install the driver in Device Manager and confirm the COM port

⚠️ Flashing keeps waiting, progress bar doesn't move
Cause: download mode wasn't entered, or the cable only charges and can't transfer data
Fix: press and hold EN during flashing to enter download mode as prompted; try a Type-C data-capable cable

⚠️ make reports Makefile not found
Cause: the build command ran in the wrong directory (must be inside the example project directory)
Fix: run cd ~/Ai-Thinker-WB2/applications/iot-solution/demo_sht3x first, then make -j8

Self-Check

The serial prints temperature/humidity every second; breathing raises the humidity, pinching raises the temperature, and no N/A appears — the SHT30 measurement is verified.

Released under the MIT License. Build Time 2026-09-30 17:31:25