ESP32は、Wi-FiやBluetoothだけでなく、アナログ信号を読み取るためのADC(Analog to Digital Converter)も搭載しています。
温度センサーや可変抵抗、光センサーなど、多くのデバイスはアナログ電圧を出力します。
ESP32のADCを利用することで、これらのアナログ信号をデジタルデータとして取得し、さまざまな制御やデータ処理に活用できます。
ESP32で開発を始めると、
- ADCとはどのような機能なの?
- ADC1とADC2は何が違うの?
- 分解能とは何を意味するの?
- なぜ思ったより精度が出ないことがあるの?
といった疑問を持つ方も多いでしょう。
この記事では、ADCの基本からESP32で利用する際の注意点、実務で押さえておきたいポイントまで分かりやすく解説します。
ADCとは?
ADC(Analog to Digital Converter)は、アナログ電圧をデジタル値へ変換する機能です。
例えば、
- 可変抵抗を回す
- 光の明るさが変わる
- 温度が変化する
こうした現象は、多くの場合「電圧の変化」として表れます。
しかし、マイコンは電圧をそのまま理解することはできません。
そこでADCを利用して電圧を数値へ変換し、プログラムで扱えるようにします。
ESP32のADCの概要
ESP32には複数のADCチャネルが搭載されており、複数のアナログ入力を扱うことができます。
入力された電圧は一定の分解能でデジタル値へ変換されます。
ESP32シリーズでは、デバイスごとに利用できるADCチャネル数や性能が異なるため、使用する製品のデータシートを確認することが重要です。
また、ESP32のADCは一般的な計測器ほど高精度ではなく、簡易的な電圧測定やセンサー入力を目的として利用されることが多くあります。
ADC1とADC2の違い
ESP32(初代)では、ADCはADC1とADC2の2つに分かれています。
特に注意したいのは、ADC2はWi-Fi機能と内部リソースを共有している点です。
そのため、Wi-Fiを使用している間はADC2が利用できなかったり、期待どおりに動作しなかったりすることがあります。
一方、ADC1はWi-Fiの影響を受けずに利用できるため、Wi-Fiを使用するアプリケーションではADC1を選ぶことが一般的です。
なお、ESP32-S2、ESP32-S3、ESP32-C3、ESP32-C5などではADCの構成が異なるため、シリーズごとの仕様を確認してください。
分解能と入力電圧
ADCは入力された電圧を一定の段階に分けて数値へ変換します。
この段階の細かさを分解能と呼びます。
一般的には、
- 分解能が高いほど細かな電圧変化を検出できる
- 分解能が低いほど変化を大まかにしか判別できない
という特徴があります。
ただし、分解能が高くてもノイズの影響を受ければ、実際の測定精度が向上するとは限りません。
ADCでは、分解能だけでなく、回路設計やノイズ対策も重要になります。
ADCが使われる場面
ESP32のADCは、次のような用途でよく利用されます。
- 可変抵抗の読み取り
- 温度センサー(アナログ出力タイプ)
- 光センサー
- バッテリー電圧の監視
- 電流センサー
- アナログジョイスティック
これらは、連続的に変化する値を取得したい場合に適しています。
一方、I²CやSPI対応のデジタルセンサーではADCは不要です。
用途に応じて、アナログ入力とデジタル通信を使い分けることが重要です。
今回使用する開発環境
この記事では、次の環境を使用してADCの動作を確認します。
- ESP-IDF:v5.5系
- ターゲット:ESP32-C5
- 開発環境:Visual Studio Code
- ADC入力:GPIO1
- 入力回路:10kΩ可変抵抗
ESP32-C5では、GPIO1~GPIO6をADC入力として使用できます。
今回のサンプルでは、GPIO1をADC入力として使用します。
使用するESP32シリーズや開発ボードによってADC対応GPIOは異なるため、別のデバイスで試す場合はデータシートを確認してください。
ESP32-C5ではADC入力がGPIO1~GPIO6へ割り当てられており、GPIO1はADC1_CH0です。
可変抵抗を使ったADC入力回路
今回は、10kΩの可変抵抗を使って0V~3.3Vの電圧をADCへ入力します。
接続は次のとおりです。
ESP32-C5 10kΩ可変抵抗
3.3V ─────────── 端子1
│
│
GPIO1(ADC1_CH0)───── 中央端子
│
│
GND ─────────── 端子3
可変抵抗の両端を3.3VとGNDへ接続し、中央端子であるワイパ端子をGPIO1へ接続します。
可変抵抗を回すとGPIO1へ入力される電圧が変化し、ADCの変換値も変化します。
配線時の注意点
ADC入力へESP32-C5の許容範囲を超える電圧を加えてはいけません。
また、可変抵抗の中央端子とGPIO1の間の配線は、できるだけ短くしてください。
測定値が大きく変動する場合は、GPIO1とGNDの間に0.1µF程度のセラミックコンデンサを追加すると、ノイズを低減できる場合があります。
ESP-IDFでADCを使用する流れ
ESP-IDFでは、ADCを利用する際に専用のドライバを使用します。
基本的な流れは次のとおりです。
- ADCユニットを初期化する
- 使用するチャネルを設定する
- 減衰率(Attenuation)を設定する
- 必要に応じてキャリブレーションを実施する
- ADC値を取得する
ESP-IDFでは、キャリブレーション機能を利用することで、実際の入力電圧に近い値へ補正できます。
単にADC値を取得するだけではなく、補正まで行うことでより実用的な測定が可能になります。
CMakeLists.txtの設定
ADCのOneshotドライバとキャリブレーション機能を使用するため、mainフォルダ内のCMakeLists.txtを次のように記述します。
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES esp_adc
)
ESP-IDF v5系では、ADCの新しいドライバはesp_adcコンポーネントとして提供されています。
以前のdriver/adc.hを使用する方式は旧APIであるため、この記事では新しいOneshotドライバを使用します。
ESP32-C5でADC値を読み取るサンプルコード
以下は、GPIO1へ入力された電圧を500msごとに測定し、ADCの生データとキャリブレーション後の電圧を表示するサンプルです。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#define ADC_INPUT_GPIO 1
#define ADC_ATTENUATION ADC_ATTEN_DB_12
#define ADC_BIT_WIDTH ADC_BITWIDTH_DEFAULT
#define ADC_READ_INTERVAL 500
static const char *TAG = "ADC_SAMPLE";
void app_main(void)
{
adc_unit_t adc_unit;
adc_channel_t adc_channel;
/*
* GPIO番号からADCユニットとADCチャネルを取得します。
* GPIO1はESP32-C5のADC入力端子です。
*/
ESP_ERROR_CHECK(
adc_oneshot_io_to_channel(
ADC_INPUT_GPIO,
&adc_unit,
&adc_channel
)
);
/*
* ADCユニットをOneshotモードで初期化します。
*/
adc_oneshot_unit_handle_t adc_handle;
adc_oneshot_unit_init_cfg_t unit_config = {
.unit_id = adc_unit,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(
adc_oneshot_new_unit(
&unit_config,
&adc_handle
)
);
/*
* ADCチャネルの分解能と減衰率を設定します。
*/
adc_oneshot_chan_cfg_t channel_config = {
.bitwidth = ADC_BIT_WIDTH,
.atten = ADC_ATTENUATION,
};
ESP_ERROR_CHECK(
adc_oneshot_config_channel(
adc_handle,
adc_channel,
&channel_config
)
);
/*
* ESP32-C5のCurve Fitting方式で
* ADCキャリブレーションを初期化します。
*/
adc_cali_handle_t calibration_handle = NULL;
adc_cali_curve_fitting_config_t calibration_config = {
.unit_id = adc_unit,
.chan = adc_channel,
.atten = ADC_ATTENUATION,
.bitwidth = ADC_BIT_WIDTH,
};
ESP_ERROR_CHECK(
adc_cali_create_scheme_curve_fitting(
&calibration_config,
&calibration_handle
)
);
ESP_LOGI(
TAG,
"ADC measurement started: GPIO%d",
ADC_INPUT_GPIO
);
while (1)
{
int raw_value = 0;
int voltage_mv = 0;
/*
* ADCの生データを取得します。
*/
ESP_ERROR_CHECK(
adc_oneshot_read(
adc_handle,
adc_channel,
&raw_value
)
);
/*
* 生データをキャリブレーション済みの
* 電圧値(mV)へ変換します。
*/
ESP_ERROR_CHECK(
adc_cali_raw_to_voltage(
calibration_handle,
raw_value,
&voltage_mv
)
);
ESP_LOGI(
TAG,
"Raw: %d, Voltage: %d mV",
raw_value,
voltage_mv
);
vTaskDelay(
pdMS_TO_TICKS(ADC_READ_INTERVAL)
);
}
}
このプログラムでは、ESP-IDF v5系のOneshotドライバを使用しています。
adc_oneshot_read()でADCの生データを取得し、adc_cali_raw_to_voltage()でキャリブレーション後の電圧値へ変換しています。
サンプルコードのポイント
GPIO番号からADCチャネルを取得する
adc_oneshot_io_to_channel(
ADC_INPUT_GPIO,
&adc_unit,
&adc_channel
);
ESP32シリーズでは、GPIO番号とADCチャネル番号の対応がデバイスごとに異なります。
このサンプルでは、GPIO番号からADCユニットとチャネルを自動的に取得しています。
ADCに対応していないGPIOを指定するとエラーになるため、GPIO番号の設定ミスにも気づきやすくなります。
ADCユニットを初期化する
adc_oneshot_new_unit(
&unit_config,
&adc_handle
);
ADCをOneshotモードで使用するためのハンドルを作成します。
Oneshotモードは、必要なタイミングで1回ずつADC値を読み取る用途に適しています。
ADCチャネルを設定する
adc_oneshot_config_channel(
adc_handle,
adc_channel,
&channel_config
);
ここではADCの分解能とAttenuationを設定しています。
今回のサンプルでは、入力範囲を広く取るためにADC_ATTEN_DB_12を使用しています。
ADCの生データを取得する
adc_oneshot_read(
adc_handle,
adc_channel,
&raw_value
);
raw_valueにはADCの生の変換結果が格納されます。
この値は、そのままでは正確な電圧を表しているわけではありません。
電圧値へ変換する
adc_cali_raw_to_voltage(
calibration_handle,
raw_value,
&voltage_mv
);
ADCの生データを、キャリブレーション情報を使って電圧へ変換します。
変換結果はmV単位で取得できます。
実行結果
プログラムを書き込み、シリアルモニターを開くと、次のようなログが表示されます。
I (325) ADC_SAMPLE: ADC measurement started: GPIO1
I (825) ADC_SAMPLE: Raw: 428, Voltage: 341 mV
I (1325) ADC_SAMPLE: Raw: 1642, Voltage: 1327 mV
I (1825) ADC_SAMPLE: Raw: 3015, Voltage: 2458 mV
I (2325) ADC_SAMPLE: Raw: 3984, Voltage: 3251 mV
可変抵抗を回すと、RawとVoltageの値が変化します。
Raw:ADCの生データVoltage:キャリブレーション後の入力電圧- 電圧の単位:mV
実際に表示される値は、使用するESP32-C5の個体差、電源電圧、可変抵抗、配線、ノイズなどによって異なります。
OneshotモードとContinuousモードの違い
ESP-IDFのADCドライバには、主にOneshotモードとContinuousモードがあります。
Oneshotモードは、温度や可変抵抗、バッテリー電圧などを一定間隔で測定する用途に適しています。
一方、Continuousモードは、ADCデータを高速かつ連続的に取得する用途に適しており、DMAを利用してデータをメモリへ転送します。
今回のサンプルは500msごとに電圧を確認するため、構成がシンプルなOneshotモードを使用しています。
Attenuation(減衰率)とは?
ESP32では、入力電圧の範囲に応じて**Attenuation(減衰率)**を設定できます。
減衰率を適切に設定することで、測定できる入力電圧範囲を変更できます。
例えば、低い電圧を高い分解能で測定したい場合と、より高い電圧を測定したい場合では、最適な設定が異なります。
入力電圧に合わない設定を選ぶと、測定値が飽和したり、十分な分解能を得られなかったりするため、データシートに記載された入力範囲を確認して設定しましょう。
ADCでよくあるトラブル
ADCはデジタル通信と違い、周囲の環境や回路設計の影響を受けやすい機能です。
実務でよくあるトラブルを紹介します。
1.測定値が安定しない
ADC値が毎回変化する原因として、
- 電源ノイズ
- センサー出力の揺らぎ
- 配線からのノイズ
などが考えられます。
単純に1回だけ読み取るのではなく、複数回測定して平均化することで、より安定した値を得られる場合があります。
2.Wi-Fi使用時にADC2が利用できない
ESP32(初代)では、ADC2はWi-Fi機能と内部リソースを共有しています。
そのため、Wi-Fiを利用するアプリケーションでは、ADC2が正常に利用できないことがあります。
Wi-Fiを使用する予定がある場合は、ADC1を使用する設計がおすすめです。
3.入力電圧が範囲を超えている
ADCの入力範囲を超える電圧を加えると、正しい値を取得できません。
場合によってはデバイスを破損する可能性もあります。
分圧回路や保護回路を設け、入力電圧が許容範囲を超えないよう設計しましょう。
実務での設計ポイント
実際の製品開発では、ADCの精度はソフトウェアだけでは決まりません。
私は次の点を特に意識しています。
- アナログ配線をできるだけ短くする
- GNDを安定させる
- デジタル信号と並走させない
- 電源ノイズ対策を行う
- キャリブレーションを実施する
- 平均化やフィルタ処理を組み合わせる
ADCは「回路設計」と「ソフトウェア処理」の両方を最適化して初めて、本来の性能を発揮できます。
まとめ
ADCは、ESP32でアナログ信号を扱うために欠かせない機能です。
可変抵抗や温度センサー、電圧監視など、多くの用途で利用されます。
一方で、Wi-Fiとの競合やノイズの影響、入力電圧範囲など、設計時に注意すべきポイントも少なくありません。
ESP32のADCを正しく理解し、適切な回路設計とソフトウェア処理を組み合わせることで、安定した測定結果を得られるようになります。
関連記事
ESP32 UART完全ガイド|UART0・UART1・UART2の違いと使い方を現役エンジニアが解説
ESP32 SPI完全ガイド|SPI通信の基本と使い方を現役エンジニアが解説
ESP32 I²C完全ガイド|I²C通信の基本と使い方を現役エンジニアが解説
ESP32 BLEとWi-Fiは同時に使える?現役エンジニアが仕組みと注意点を解説
ESP32 Deep Sleepとは?消費電力を大幅に削減する省電力機能を現役エンジニアが解説
技術は、経験から価値になる。
DLROW Design|現場エンジニア