ESP-IDFを使ってみる

ADC変換とDAC変換


ESP32/ESP32S2/ESP32S3/ESP32C3(8685)はADC1、ADC2の2系統のADCコンバータを搭載しています。
ESP32C2(8684)/C6ではADC2は廃止され、ADC1だけになりました。
こ ちらにADCのサンプルが公開されていますが、モデルごとに挙動が違うので結構面倒です。

・ADC1もADC2もサンプリングのデフォルト解像度がモデルごとに異なります。
・キャリブレーションの方法がモデルごとに違います。
・使用できるチャネル数とGPIOがモデルごとに違います。

以下のコードでこの違いを確認することができます。
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "soc/adc_channel.h"
#include "esp_adc/adc_oneshot.h"
// for ADC_WIDTH_BIT_DEFAULT
#include "driver/adc_types_legacy.h"

// convert from gpio to adc1 channel
adc_channel_t gpio2adc(int gpio) {
#if CONFIG_IDF_TARGET_ESP32
        if (gpio == 32) return ADC1_GPIO32_CHANNEL;
        if (gpio == 33) return ADC1_GPIO33_CHANNEL;
        if (gpio == 34) return ADC1_GPIO34_CHANNEL;
        if (gpio == 35) return ADC1_GPIO35_CHANNEL;
        if (gpio == 36) return ADC1_GPIO36_CHANNEL;
        if (gpio == 37) return ADC1_GPIO37_CHANNEL;
        if (gpio == 38) return ADC1_GPIO38_CHANNEL;
        if (gpio == 39) return ADC1_GPIO39_CHANNEL;

#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        if (gpio == 1) return ADC1_GPIO1_CHANNEL;
        if (gpio == 2) return ADC1_GPIO2_CHANNEL;
        if (gpio == 3) return ADC1_GPIO3_CHANNEL;
        if (gpio == 4) return ADC1_GPIO4_CHANNEL;
        if (gpio == 5) return ADC1_GPIO5_CHANNEL;
        if (gpio == 6) return ADC1_GPIO6_CHANNEL;
        if (gpio == 7) return ADC1_GPIO7_CHANNEL;
        if (gpio == 8) return ADC1_GPIO8_CHANNEL;
        if (gpio == 9) return ADC1_GPIO9_CHANNEL;
        if (gpio == 10) return ADC1_GPIO10_CHANNEL;

#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3
        if (gpio == 0) return ADC1_GPIO0_CHANNEL;
        if (gpio == 1) return ADC1_GPIO1_CHANNEL;
        if (gpio == 2) return ADC1_GPIO2_CHANNEL;
        if (gpio == 3) return ADC1_GPIO3_CHANNEL;
        if (gpio == 4) return ADC1_GPIO4_CHANNEL;

#elif CONFIG_IDF_TARGET_ESP32C6
        if (gpio == 0) return ADC1_GPIO0_CHANNEL;
        if (gpio == 1) return ADC1_GPIO1_CHANNEL;
        if (gpio == 2) return ADC1_GPIO2_CHANNEL;
        if (gpio == 3) return ADC1_GPIO3_CHANNEL;
        if (gpio == 4) return ADC1_GPIO4_CHANNEL;
        if (gpio == 5) return ADC1_GPIO5_CHANNEL;
        if (gpio == 6) return ADC1_GPIO6_CHANNEL;

#endif
        return -1;
}

void app_main(void)
{
        adc_channel_t adc1_channel;
        int adc1_gpio;
#if CONFIG_IDF_TARGET_ESP32
        printf("TARGET_ESP32\n");
        adc1_gpio = 32;
        adc1_channel = gpio2adc(adc1_gpio);
#elif CONFIG_IDF_TARGET_ESP32S2
        printf("TARGET_ESP32S2\n");
        adc1_gpio = 1;
        adc1_channel = gpio2adc(adc1_gpio);
#elif CONFIG_IDF_TARGET_ESP32S3
        printf("TARGET_ESP32S3\n");
        adc1_gpio = 1;
        adc1_channel = gpio2adc(adc1_gpio);
#elif CONFIG_IDF_TARGET_ESP32C2
        printf("TARGET_ESP32C2\n");
        adc1_gpio = 0;
        adc1_channel = gpio2adc(adc1_gpio);
#elif CONFIG_IDF_TARGET_ESP32C3
        printf("TARGET_ESP32C3\n");
        adc1_gpio = 0;
        adc1_channel = gpio2adc(adc1_gpio);
#elif CONFIG_IDF_TARGET_ESP32C6
        printf("TARGET_ESP32C6\n");
        adc1_gpio = 0;
        adc1_channel = gpio2adc(adc1_gpio);
#endif

#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
        printf("SUPPORT ADC_CALI_SCHEME_CURVE_FITTING\n");
#else
        printf("NOT SUPPORT ADC_CALI_SCHEME_CURVE_FITTING\n");
#endif

        // ADC default bit width
        printf("ADC_WIDTH_BIT_DEFAULT=%d\n", ADC_WIDTH_BIT_DEFAULT);
        printf("ADC_BITWIDTH_DEFAULT=%d\n", ADC_BITWIDTH_DEFAULT);
        // ADC Max channel
        printf("SOC_ADC_MAX_CHANNEL_NUM=%d\n", SOC_ADC_MAX_CHANNEL_NUM);
        // for continuous mode
        printf("SOC_ADC_DIGI_MIN_BITWIDTH=%d\n", SOC_ADC_DIGI_MIN_BITWIDTH);
        printf("SOC_ADC_DIGI_MAX_BITWIDTH=%d\n", SOC_ADC_DIGI_MAX_BITWIDTH);
        // for oneshot mode
        printf("SOC_ADC_RTC_MIN_BITWIDTH=%d\n", SOC_ADC_RTC_MIN_BITWIDTH);
        printf("SOC_ADC_RTC_MAX_BITWIDTH=%d\n", SOC_ADC_RTC_MAX_BITWIDTH);


        // ADC1 Init
        adc_oneshot_unit_handle_t adc1_handle;
        adc_oneshot_unit_init_cfg_t init_config1 = {
                .unit_id = ADC_UNIT_1,
        };
        ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));

        // ADC1 config
        adc_oneshot_chan_cfg_t config = {
                .bitwidth = ADC_BITWIDTH_DEFAULT,
                .atten = ADC_ATTEN_DB_0,
        };
        printf("adc1_gpio=%d adc1_channel=%d\n", adc1_gpio, adc1_channel);
        ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, adc1_channel, &config));

        //for (int i=0;i<10;i++) {
        while (1) {
                int adc_raw;
                ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, adc1_channel, &adc_raw));
                printf("GPIO%02d adc1_channel: %d Raw: %d(0x%04x)\n", adc1_gpio, adc1_channel, adc_raw, adc_raw);
                vTaskDelay(100);
        }
}

adc1_gpioのピンと3.3Vピンをワイヤーで接続して、ESP32/ESP32S2/ESP32S3/ESP32C2/ESP32C3 /ESP32C6でこのコードを実行すると以下の様になります。
TARGET_ESP32
NOT SUPPORT ADC_CALI_SCHEME_CURVE_FITTING
SUPPORT ADC_CALI_SCHEME_LINE_FITTING
ADC_WIDTH_BIT_DEFAULT=12
ADC_BITWIDTH_DEFAULT=0
SOC_ADC_MAX_CHANNEL_NUM=10
SOC_ADC_DIGI_MIN_BITWIDTH=9
SOC_ADC_DIGI_MAX_BITWIDTH=12
SOC_ADC_RTC_MIN_BITWIDTH=9
SOC_ADC_RTC_MAX_BITWIDTH=12
adc1_gpio=32 adc1_channel=4
GPIO32 adc1_channel: 4 Raw: 4095(0x0fff)


TARGET_ESP32S2
NOT SUPPORT ADC_CALI_SCHEME_CURVE_FITTING
SUPPORT ADC_CALI_SCHEME_LINE_FITTING
ADC_WIDTH_BIT_DEFAULT=13
ADC_BITWIDTH_DEFAULT=0
SOC_ADC_MAX_CHANNEL_NUM=10
SOC_ADC_DIGI_MIN_BITWIDTH=12
SOC_ADC_DIGI_MAX_BITWIDTH=12
SOC_ADC_RTC_MIN_BITWIDTH=13
SOC_ADC_RTC_MAX_BITWIDTH=13
adc1_gpio=1 adc1_channel=0
GPIO01 adc1_channel: 0 Raw: 8191(0x1fff)


TARGET_ESP32S3
SUPPORT ADC_CALI_SCHEME_CURVE_FITTING
NOT SUPPORT ADC_CALI_SCHEME_LINE_FITTING
ADC_WIDTH_BIT_DEFAULT=12
ADC_BITWIDTH_DEFAULT=0
SOC_ADC_MAX_CHANNEL_NUM=10
SOC_ADC_DIGI_MIN_BITWIDTH=12
SOC_ADC_DIGI_MAX_BITWIDTH=12
SOC_ADC_RTC_MIN_BITWIDTH=12
SOC_ADC_RTC_MAX_BITWIDTH=12
adc1_gpio=1 adc1_channel=0
GPIO01 adc1_channel: 0 Raw: 4095(0x0fff)


TARGET_ESP32C2
NOT SUPPORT ADC_CALI_SCHEME_CURVE_FITTING
SUPPORT ADC_CALI_SCHEME_LINE_FITTING
ADC_WIDTH_BIT_DEFAULT=12
ADC_BITWIDTH_DEFAULT=0
SOC_ADC_MAX_CHANNEL_NUM=5
SOC_ADC_DIGI_MIN_BITWIDTH=12
SOC_ADC_DIGI_MAX_BITWIDTH=12
SOC_ADC_RTC_MIN_BITWIDTH=12
SOC_ADC_RTC_MAX_BITWIDTH=12
adc1_gpio=0 adc1_channel=0
GPIO00 adc1_channel: 0 Raw: 4095(0x0fff)

TARGET_ESP32C3
SUPPORT ADC_CALI_SCHEME_CURVE_FITTING
NOT SUPPORT ADC_CALI_SCHEME_LINE_FITTING
ADC_WIDTH_BIT_DEFAULT=12
ADC_BITWIDTH_DEFAULT=0
SOC_ADC_MAX_CHANNEL_NUM=5
SOC_ADC_DIGI_MIN_BITWIDTH=12
SOC_ADC_DIGI_MAX_BITWIDTH=12
SOC_ADC_RTC_MIN_BITWIDTH=12
SOC_ADC_RTC_MAX_BITWIDTH=12
adc1_gpio=0 adc1_channel=0
GPIO00 adc1_channel: 0 Raw: 4095(0x0fff)


TARGET_ESP32C6
SUPPORT ADC_CALI_SCHEME_CURVE_FITTING
NOT SUPPORT ADC_CALI_SCHEME_LINE_FITTING
ADC_WIDTH_BIT_DEFAULT=12
ADC_BITWIDTH_DEFAULT=0
SOC_ADC_MAX_CHANNEL_NUM=7
SOC_ADC_DIGI_MIN_BITWIDTH=12
SOC_ADC_DIGI_MAX_BITWIDTH=12
SOC_ADC_RTC_MIN_BITWIDTH=12
SOC_ADC_RTC_MAX_BITWIDTH=12
adc1_gpio=0 adc1_channel=0
GPIO00 adc1_channel: 0 Raw: 4081(0x0ff1) ----> なぜか4095にならない

サンプリングのデフォルトの解像度は、ESP32S2は13Bitsで、それ以外は12Bitsです。
キャリブレーションの方法として ADC_CALI_SCHEME_CURVE_FITTING/ADC_CALI_SCHEME_LINE_FITTINGが有り、
モデルごとにサポートしているキャリブレーション方法が違いますが、
公式サンプルの様に、両方のコードを書いておけば問題になる事は有ません。
キャリブレーションは読み取ったADCの値から電圧値に換算するときに使う補正値なので、
ADCの生の値を使う場合は、キャリブレーションを行う必要は有りません。



ESP-IDF V5からADCのドライバーは以下の3つに分離されました。
・ADC Oneshot Mode Driver
・ADC Continuous Mode Driver
・ADC Calibration Driver

APIもOneshot ModeとContinuous Modeでは別々のAPIに分かれました。
ESP-IDFのissuesを見ているとcontinuous modeにはいろいろ問題があるようです。
oneshot modeのサンプリングの解像度はadc_oneshot_config_channel()で指定しますが、ADC_BITWIDTH_DEFAULT 以外に ビット数を指 定することができます。
指定できるビット数はモデルごとに異なります。

ESP32 ESP32S2 ESP32S3 ESP32C2/C3/C6
ADC_WIDTH_BIT_9 OK NG NG NG
ADC_WIDTH_BIT_10 OK NG NG NG
ADC_WIDTH_BIT_11 OK NG NG NG
ADC_WIDTH_BIT_12 OK(デフォルト) NG OK OK
ADC_WIDTH_BIT_13 NG OK NG NG

これを見てわかるように、全てのモデルで共通のサンプリング解像度はありません。
ESP32S2だけは13 ビット→12ビットにシフトして、12ビットの解像度にそろえる必要が有ります。

「ADC_BITWIDTH_DEFAULT」のマクロ値は0なので、デフォルトが何ビット幅なのか分かりません。
メチャクチャ紛らわしいですが、「ADC_WIDTH_BIT_DEFAULT」と言うマクロが有って、これに12とか13の値が設定されていま す。
但し、このマクロはESP-IDF V5で非標準になってしまいました。
ESP-IDF V5でこの値を使うときは、「driver/adc_types_legacy.h」をインクルードする必要が有ります。
なお、電圧値を求める場合はesp_adc_cal_raw_to_voltage()を使えば、関数内部で解像度を判断して、キャリブレーショ ンによる補正を行った上で、
解像度や個体差に依存しな い電圧値を得られることになっていますが、ESP32C6は少し怪しい動きをします。



ESP32ではADC1とADC2の2つのADCを使うことができます。
ESP-IDF V4.xまでは、ADC1とADC2では、それぞれ別の定義や関数を使っていましたが、ESP-IDF V5ではADC1もADC2も共通の定義、関数になり、
キャリブレーションの関数もADC1とADC2で統一されまし た。
公式のサンプルコードがこ ちらに公開されていますが、DMAを使わないOneShotの読み取りと、DMAを使った連続読み取りのサンプルに整理されてシンプ ルになり ました。

1つのソースで、V4.xとV5.xのどちらでも動くようにするのはかなり面倒です。
以下の様に、V4.xとV5.xではリンクするソースを分けた方が簡単です。
$ cat CMakeLists.txt

if (IDF_VERSION_MAJOR STREQUAL "4")
    set(srcs "main_v4.c")
endif()
if (IDF_VERSION_MAJOR STREQUAL "5")
    set(srcs "main_v5.c")
endif()

idf_component_register(SRCS ${srcs} INCLUDE_DIRS ".")

1つのソースの中で、V4.xとV5.xに対応しようとすると、このようになります。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_adc/adc_oneshot.h"
#else
#include "driver/adc.h"
#endif

#define TAG "MAIN"

void app_main(void)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
        // ADC1 Init
        adc_oneshot_unit_handle_t adc1_handle;
        adc_oneshot_unit_init_cfg_t init_config1 = {
                .unit_id = ADC_UNIT_1,
        };
        ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));

        // ADC1 config
        adc_oneshot_chan_cfg_t config = {
                .bitwidth = ADC_BITWIDTH_DEFAULT,
                .atten = ADC_ATTEN_DB_11,
        };
        adc_channel_t adc_channel = ADC_CHANNEL_0;
        ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, adc_channel, &config));
#else
        // ADC1 config
        ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_DEFAULT));
        adc1_channel_t adc_channel = ADC1_CHANNEL_0;
        ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, ADC_ATTEN_DB_11));
#endif

        while(1) {
                int adc_raw;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
                ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC_CHANNEL_0, &adc_raw));
#else
                adc_raw = adc1_get_raw(ADC1_CHANNEL_0);
#endif
                ESP_LOGI(TAG, "adc_raw=%d", adc_raw);
                vTaskDelay(50);
        }
}

上記のコードはキャリブレーションを行わないサンプルです。
キャリブレーションのコードを追加すると、さらに複雑になり可読性も悪くなります。



ESP32とESP32S2はDACコンバータを搭載しています。
ESP32S3やESP32CシリーズにはDACは搭載されていません。
ESP32でDACとして使用できるGPIOはGPIO25とGPIO26だけで、ESP32S2でDACとして使用できるGPIOは GPIO17とGPIO18だけす。
どちらも0から255までのDigital値をAnalog値に変換することができます。
出力電圧は out_voltage = Vref * digi_val / 255 で、Vrefは電源電圧(3.3V)になります。
ESP-IDF V5.1からLegacy DAC driverとNew DAC driverに分かれました。
さらにNew DAC driverはone-shot mode、cosine mode、continuous modeにAPIが別れました。
こ ちらにそれぞれのサンプルが公開されています。

続く....