Skip to main content

ADC Peripheral Guide

The TKL ADC interface reads analog voltages from sensors, potentiometers, and other analog sources. You configure an ADC unit, enable channels with a bitmask, then read raw counts and convert them to millivolts.

Prerequisitesโ€‹

How ADC Works in TuyaOpenโ€‹

TuyaOpen's ADC uses a port + channel bitmask model:

  • Port selects the ADC unit: TUYA_ADC_NUM_0 = ADC1, TUYA_ADC_NUM_1 = ADC2
  • Channel bitmask (cfg->ch_list.data) enables specific channels: bit N enables ADC_CHANNEL_N
  • Attenuation is fixed at ADC_ATTEN_DB_12 (0-3.3 V full range)
  • Reference voltage is 3300 mV, returned by tkl_adc_ref_voltage_get()

Basic ADC Readโ€‹

#include "tkl_adc.h"

#define ADC_PORT TUYA_ADC_NUM_0
#define ADC_CHANNEL 4

static OPERATE_RET read_adc(uint32_t *raw_value)
{
TUYA_ADC_BASE_CFG_T cfg = {
.ch_list.data = (1 << ADC_CHANNEL),
.ch_nums = 1,
.width = 12,
.type = TUYA_ADC_INNER_SAMPLE_VOL,
};

OPERATE_RET rt = tkl_adc_init(ADC_PORT, &cfg);
if (rt != OPRT_OK) {
return rt;
}

int32_t value;
rt = tkl_adc_read_single_channel(ADC_PORT, ADC_CHANNEL, &value);
tkl_adc_deinit(ADC_PORT);

*raw_value = (uint32_t)value;
return rt;
}

Converting Raw to Voltageโ€‹

uint32_t raw;
read_adc(&raw);

uint32_t ref_mv = tkl_adc_ref_voltage_get(ADC_PORT);

float voltage = (float)raw * ref_mv / ((1 << 12) - 1);
TAL_PR_INFO("voltage: %.2f mV", voltage);

Multi-Channel Readingโ€‹

Enable multiple channels simultaneously with the bitmask:

TUYA_ADC_BASE_CFG_T cfg = {
.ch_list.data = (1 << 0) | (1 << 1) | (1 << 4),
.ch_nums = 3,
.width = 12,
};
tkl_adc_init(TUYA_ADC_NUM_0, &cfg);

int32_t ch0_val, ch1_val, ch4_val;
tkl_adc_read_single_channel(TUYA_ADC_NUM_0, 0, &ch0_val);
tkl_adc_read_single_channel(TUYA_ADC_NUM_0, 1, &ch1_val);
tkl_adc_read_single_channel(TUYA_ADC_NUM_0, 4, &ch4_val);

tkl_adc_deinit(TUYA_ADC_NUM_0);

ADC Channel-to-GPIO Mappingโ€‹

Channel-to-GPIO mapping depends on the chip. See the per-chip pinmux docs for complete tables:

PlatformADC1 PortADC2 PortPinmux Doc
ESP32TUYA_ADC_NUM_0 (GPIO 32-39)TUYA_ADC_NUM_1 (GPIO 0-27)ESP32 Classic Pinmux
ESP32-S3TUYA_ADC_NUM_0 (GPIO 1-10)TUYA_ADC_NUM_1 (GPIO 11-20)ESP32-S3 Pinmux
ESP32-C3TUYA_ADC_NUM_0 (GPIO 0-4)TUYA_ADC_NUM_1 (GPIO 5)ESP32-C3 Pinmux
ESP32-C6TUYA_ADC_NUM_0 (GPIO 0-6)N/AESP32-C6 Pinmux
T5AITUYA_ADC_NUM_0 (P0-P28)N/AT5AI Peripheral Mapping

ESP32 ADC2 and Wi-Fi Conflictโ€‹

warning

On classic ESP32, ADC2 cannot be used while Wi-Fi is active. ADC2 channels share hardware with the Wi-Fi radio. If your application uses Wi-Fi (most do), only ADC1 channels are available.

This limitation does not apply to ESP32-S3, ESP32-C3, or ESP32-C6.

Bit Width Optionsโ€‹

WidthResolutionMax Raw Value
9512 levels511
101024 levels1023
112048 levels2047
12 (default)4096 levels4095
138192 levels8191

Not all widths are available on all chips. Use 12 for best compatibility.

Temperature Sensorโ€‹

tkl_adc_temperature_get() returns OPRT_NOT_SUPPORTED on ESP32. The chip's internal temperature sensor uses a separate ESP-IDF driver (temperature_sensor_*), not the ADC path.

Practical Example: Battery Voltage Monitorโ€‹

Read a battery voltage through a resistor divider (2:1 ratio):

#define BATTERY_ADC_PORT TUYA_ADC_NUM_0
#define BATTERY_ADC_CHANNEL 5
#define DIVIDER_RATIO 2.0f

float get_battery_voltage_mv(void)
{
TUYA_ADC_BASE_CFG_T cfg = {
.ch_list.data = (1 << BATTERY_ADC_CHANNEL),
.ch_nums = 1,
.width = 12,
};
tkl_adc_init(BATTERY_ADC_PORT, &cfg);

int32_t raw;
tkl_adc_read_single_channel(BATTERY_ADC_PORT, BATTERY_ADC_CHANNEL, &raw);
tkl_adc_deinit(BATTERY_ADC_PORT);

uint32_t ref_mv = tkl_adc_ref_voltage_get(BATTERY_ADC_PORT);

float measured_mv = (float)raw * ref_mv / 4095.0f;
return measured_mv * DIVIDER_RATIO;
}

Referencesโ€‹