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-11 14:52:23