ESP-IDFでADCを使う

今回は、ESP-IDFでADCを使って見ます。今回実行する例題は、/esp/esp-idf/examples/peripherals/adc/single_read。またADCに付いての詳細はここで説明されています。

ADCに付いての簡単な説明。

  • 使用出来るポートは、
    • ADC1: 8 channels: GPIO32 – GPIO39
    • ADC2: 10 channels: GPIO0, GPIO2, GPIO4, GPIO12 – GPIO15, GOIO25 – GPIO27
    • の計18ポート
  • 0VからVrefまでの値を測定する。
  • Vrefの値は基本的に1.1Vだが、Attenuationを使用して測定範囲を広げることが可能。
Attenuation Measurable input voltage range 
 ADC_ATTEN_DB_0    100 mV ~ 950 mV
 ADC_ATTEN_DB_2_5    100 mV ~ 1250 mV
 ADC_ATTEN_DB_6   150 mV ~ 1750 mV
 ADC_ATTEN_DB_11   150 mV ~ 2450 mV
  • 測定の分解能は12ビット。adc1_config_width()関数で分解能を指定
  • adc1_get_raw()関数で電圧を測定しデジタルに変換する。
  • 測定は、測定のばらつきを考慮する必要がある。通常は何回か測定を行いその平均値を測定値とする。

サンプルプログラム、

サンプルプログラム、/esp/esp-idf/examples/peripherals/adc/single_readを下記に示します。プログラムは、mainフォルダーの下の、adc1_example_main.c”です。

hello_04.py

/* ADC1 Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"

#define DEFAULT_VREF    3900        //Use adc2_vref_to_gpio() to obtain a better estimate
#define NO_OF_SAMPLES   64          //Multisampling

static esp_adc_cal_characteristics_t *adc_chars;
#if CONFIG_IDF_TARGET_ESP32
static const adc_channel_t channel = ADC_CHANNEL_4;     //GPIO32 if ADC1, GPIO14 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
#elif CONFIG_IDF_TARGET_ESP32S2
static const adc_channel_t channel = ADC_CHANNEL_6;     // GPIO7 if ADC1, GPIO17 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_13;
#endif
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1;


static void check_efuse(void)
{
#if CONFIG_IDF_TARGET_ESP32
    //Check if TP is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("eFuse Two Point: NOT supported\n");
    }
    //Check Vref is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
        printf("eFuse Vref: Supported\n");
    } else {
        printf("eFuse Vref: NOT supported\n");
    }
#elif CONFIG_IDF_TARGET_ESP32S2
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("Cannot retrieve eFuse Two Point calibration values. Default calibration values will be used.\n");
    }
#else
#error "This example is configured for ESP32/ESP32S2."
#endif
}


static void print_char_val_type(esp_adc_cal_value_t val_type)
{
    if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
        printf("Characterized using Two Point Value\n");
    } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
        printf("Characterized using eFuse Vref\n");
    } else {
        printf("Characterized using Default Vref\n");
    }
}


void app_main(void)
{
    //Check if Two Point or Vref are burned into eFuse
    check_efuse();

    //Configure ADC
    if (unit == ADC_UNIT_1) {
        adc1_config_width(width);
        adc1_config_channel_atten(channel, atten);
    } else {
        adc2_config_channel_atten((adc2_channel_t)channel, atten);
    }

    //Characterize ADC
    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars);
    print_char_val_type(val_type);

    //Continuously sample ADC1
    while (1) {
        uint32_t adc_reading = 0;
        //Multisampling
        for (int i = 0; i < NO_OF_SAMPLES; i++) {
            if (unit == ADC_UNIT_1) {
                adc_reading += adc1_get_raw((adc1_channel_t)channel);
            } else {
                int raw;
                adc2_get_raw((adc2_channel_t)channel, width, &raw);
                adc_reading += raw;
            }
        }
        adc_reading /= NO_OF_SAMPLES;
        //Convert adc_reading to voltage in mV
        uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
        printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
  • ADCの設定
    • ADC1を使用。測定ポートはGPIO32
    • 解像度は12ビット
    • Attenuationは、ADC_ATTEN_DB_11。測定範囲150 mV ~ 2450 mV
  • 実際の測定
    • 電圧を12ビットの解像度で64回測定しその平均を算出。それを測定値とする。
    • その測定値を元に測定電圧に変換。
    • その2つの値をモニターに表示し、この操作を繰り返す。

このサンプルは問題無く動作しました。サンプル実行時には気が付かなかったのですが、サンプリング回数を減らすと値がばらつきます。測定電圧の振れか測定自体の振れか分からないですが、流石に64回測定すると安定しました。