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) exampleapplications/iot-solution/demo_sht3x; the code can be found directly in the local SDK.
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.
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:
cdis the “change directory” command — entering the SHT3x example project directory; all subsequentmakebuild andmake flashflash 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 |
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 in the project directory:
make -j8
Note:
makeis the “build” command, turning code into firmware (the program file) the board can run;-j8builds 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 — runcd toolchain/riscv/Linux && . chmod755.shfirst, then rebuild.
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 flashis the “flash” command, writing the compiled firmware into the board’s chip. Afterp=comes the serial device (change it to your computer’s actual one — check withls /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.
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_tstruct pointer, required. Key fields:config.mode(HOSAL_I2C_MODE_MASTERmaster /HOSAL_I2C_MODE_SLAVEslave),config.scl/config.sda(SCL/SDA pin numbers, this tutorial IO12/IO3),config.freq(100000standard 100kHz /400000fast 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_tstruct pointerdev_addr: slave device address (the byte after shifting the 7-bit address left by 1), e.g.0x44data: send data buffer pointer, requiredsize: bytes to send, values:1~256timeout: 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_tstruct pointerdev_addr: slave device address (same rule asmaster_send)data: receive buffer pointer, requiredsize: expected bytes to receive (this tutorial6)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), requiredlen: bytes to check, values: any positive integer (this tutorial2)
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 asprintf, required...: variadic args matching thefmtplaceholders; 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 viapdMS_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
#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.

