Writing a New I2C Sensor Driver
Step-by-step tutorial for integrating a new I2C sensor (temperature, humidity, IMU, pressure, etc.) into a TuyaOpen project. Uses the SHT3x temperature/humidity sensor as a concrete example.
Prerequisitesโ
- Completed Environment Setup
- Understanding of I2C basics (address, read/write, registers)
- Sensor datasheet with register map
Requirementsโ
- TuyaOpen SDK cloned and environment set up
- Development board (T5AI, ESP32-S3, or any supported platform)
- I2C sensor module (e.g., SHT30/SHT31, BME280, BMP280, MPU6050)
- Jumper wires, breadboard
When to Use TDL/TDD vs Direct TKLโ
| Approach | When to use | Example |
|---|---|---|
| Direct TKL I2C | Simple sensors, one-off reads, prototyping | SHT3x, BME280, BMP280 |
| Full TDL/TDD | Reusable driver shared across boards, complex lifecycle | Display panel, audio codec, touch controller |
Most I2C sensors work well with direct TKL calls. Use the TDL/TDD pattern when you need device registration, multiple instances, or board-level abstraction.
Stepsโ
Step 1: Configure I2C pinsโ
Set up the I2C bus with tkl_io_pinmux_config() before initializing:
#include "tkl_i2c.h"
#include "tkl_pinmux.h"
#define SENSOR_I2C_PORT TUYA_I2C_NUM_0
#define SENSOR_SCL_PIN TUYA_GPIO_NUM_9
#define SENSOR_SDA_PIN TUYA_GPIO_NUM_10
static OPERATE_RET sensor_i2c_init(void)
{
tkl_io_pinmux_config(SENSOR_SCL_PIN, TUYA_IIC0_SCL);
tkl_io_pinmux_config(SENSOR_SDA_PIN, TUYA_IIC0_SDA);
TUYA_IIC_BASE_CFG_T cfg = {
.role = TUYA_IIC_MODE_MASTER,
.speed = TUYA_IIC_BUS_SPEED_400K,
.addr_width = TUYA_IIC_ADDRESS_7BIT,
};
return tkl_i2c_init(SENSOR_I2C_PORT, &cfg);
}
Step 2: Write the sensor read functionโ
For SHT3x, the measurement command is 0x2400 (high repeatability, no clock stretching). The sensor returns 6 bytes: 2 temp + 1 CRC + 2 humidity + 1 CRC.
#define SHT3X_ADDR 0x44
#define SHT3X_CMD_MEAS 0x2400
static OPERATE_RET sht3x_read(float *temperature, float *humidity)
{
UINT8_T cmd[2] = { (SHT3X_CMD_MEAS >> 8), (SHT3X_CMD_MEAS & 0xFF) };
UINT8_T data[6] = {0};
OPERATE_RET rt;
rt = tkl_i2c_master_send(SENSOR_I2C_PORT, SHT3X_ADDR, cmd, 2, TRUE);
if (rt != OPRT_OK) {
return rt;
}
tal_system_sleep(20);
rt = tkl_i2c_master_receive(SENSOR_I2C_PORT, SHT3X_ADDR, data, 6, TRUE);
if (rt != OPRT_OK) {
return rt;
}
UINT16_T raw_temp = (data[0] << 8) | data[1];
UINT16_T raw_humi = (data[3] << 8) | data[4];
*temperature = -45.0f + 175.0f * ((float)raw_temp / 65535.0f);
*humidity = 100.0f * ((float)raw_humi / 65535.0f);
return OPRT_OK;
}
Step 3: Create a periodic read taskโ
#include "tal_thread.h"
#include "tal_log.h"
static void sensor_task(void *arg)
{
float temp, humi;
sensor_i2c_init();
while (1) {
if (sht3x_read(&temp, &humi) == OPRT_OK) {
TAL_PR_INFO("temp: %.2f C, humidity: %.2f %%", temp, humi);
} else {
TAL_PR_ERR("sensor read failed");
}
tal_system_sleep(2000);
}
}
void tuya_app_main(void)
{
THREAD_HANDLE handle;
THREAD_CFG_T cfg = {
.thrdname = "sensor",
.stackDepth = 4096,
.priority = THREAD_PRIO_3,
};
tal_thread_create_and_start(&handle, NULL, NULL, sensor_task, NULL, &cfg);
}
Step 4: Add to your projectโ
Create the project structure:
apps/my_sensor_app/
โโโ CMakeLists.txt
โโโ src/
โ โโโ tuya_main.c (contains tuya_app_main + sensor code)
โโโ include/
โ โโโ sht3x.h (optional: separate header)
โโโ config/
โ โโโ ESP32-S3.config (or your board config)
โโโ Kconfig
CMakeLists.txt:
set(APP_NAME my_sensor_app)
Enable I2C in your board config:
CONFIG_ENABLE_I2C=y
Step 5: Build and testโ
cd apps/my_sensor_app
tos.py config choice # Select your board
tos.py build
tos.py flash
tos.py monitor
Expected output:
[01-01 00:00:02 TUYA I][tuya_main.c:xx] temp: 25.43 C, humidity: 48.21 %
[01-01 00:00:04 TUYA I][tuya_main.c:xx] temp: 25.51 C, humidity: 47.89 %
Adding CRC Validationโ
Production code should validate the CRC-8 bytes:
static UINT8_T sht3x_crc8(UINT8_T *data, UINT8_T len)
{
UINT8_T crc = 0xFF;
for (UINT8_T i = 0; i < len; i++) {
crc ^= data[i];
for (UINT8_T bit = 0; bit < 8; bit++) {
crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
}
}
return crc;
}
if (sht3x_crc8(&data[0], 2) != data[2] ||
sht3x_crc8(&data[3], 2) != data[5]) {
return OPRT_CRC32_FAILED;
}
Cross-Platform Considerationsโ
This sensor code is portable because it only uses TKL I2C APIs. The same code runs on T5AI, ESP32, Raspberry Pi, and other platforms -- only the pin numbers change.
To make pin numbers configurable, use Kconfig:
config SENSOR_I2C_PORT
int "I2C port"
default 0
config SENSOR_SCL_PIN
int "SCL pin"
default 9
config SENSOR_SDA_PIN
int "SDA pin"
default 10
Sending Sensor Data to Tuya Cloud (Optional)โ
To report sensor data as Tuya Cloud data points:
#include "tuya_iot.h"
OPERATE_RET report_sensor_data(float temp, float humi)
{
dp_obj_t dp_temp = {
.dpid = 1,
.type = PROP_VALUE,
.value.dp_value = (int)(temp * 10),
};
dp_obj_t dp_humi = {
.dpid = 2,
.type = PROP_VALUE,
.value.dp_value = (int)(humi * 10),
};
return dev_report_dp_json_async(NULL, &dp_temp, 1);
return dev_report_dp_json_async(NULL, &dp_humi, 1);
}
This requires a Tuya Cloud product with matching DP definitions. See Creating a New Product.