概要
関数を使うだけで簡単に12ビットの AD 変換が出来ます。非常に便利です。今回は AD 変換の説明です。
AD 変換器
ユニットは 2つ
ESP32 は 2つの 12ビットADCユニット( ADC1 と ADC2)を持っています。
| ユニット | GPIOピン | 説明 |
|---|---|---|
| ADC1 | 32, 33, 34, 35, 36, 37, 38, 39 | メインユニット。 Wi-Fi使用時でも使用可。 |
| ADC2 | 0, 2, 4, 12, 13, 14, 15, 25, 26, 27 | Wi-Fi使用中は使用不可。 ストラップピンがある。 |
ADC1,2合わせるとチャンネルは 18個ですが ADC2 は制約が多く、実際は縛りの無い ADC1の使用がほとんど。 それでも 8個のチャンネルが有ります。
測定範囲
ESP32は内部に基準電圧1100mVを持っていてこれと対象を比較しAD変換を行います。 よって測定範囲は基準電圧までですが、減衰器を使えば測定範囲を広げることが出来ます。
減衰器は 3つ。減衰器無しと合わせて 4レンジの測定が可能です。 最大の減衰器を使用した場合、測定範囲は 0から3.9Vとなります。
| 減衰器 | 測定範囲(約)mV |
|---|---|
| ADC_0db | 0〜1100 |
| ADC_2_5db | 0〜1467 |
| ADC_6db | 0〜2195 |
| ADC_11db | 0〜3903 |
- 減衰の割合に比例して測定精度が落ちる様に思えます。実機で確認下さい。
- 測定範囲の両端(測定範囲の 0V 付近と MAX電圧付近)でのリニアリティーが悪い。
- 測定精度が落ちる為、使用を避けた方が良い。
- 測定範囲の両端 1割カットを目安に実機で調整。
- 測定範囲が3.9V以上の時は分圧を使用
- 例えばV2( > 3.9V)を測定したい場合、抵抗を使って下記の様な回路を作る
- V1は V1= V2 * R1 / (R1 + R2) 。
- この V1 が < 3.9Vとなる様に R1,R2 を設定すれば V1 を ESP32 で測定出来ます。
- V1 が測定できれば V2 は V2 = V1 / R1 * (R1 + R2) で求める事が出来ます。
実際の測定
回路
測定対象の電圧を 12V近辺として製作
- V2:12V。R2:220kΩ R1:51kΩ
- V1 = 12 * 51 / (220 + 51) ≒ 2260mV
- これを ESP32 の GPIO36 につないで測定
使用したスケッチ
void setup(){
Serial.begin(115200);
}
void loop(){
int a,v;
pinMode(36, ANALOG);
analogSetAttenuation(ADC_11db);
v = 0;
for(a = 0; a < 10; a++){
v += analogRead(36);
delay(50);
}
v /= 10;
Serial.println("V1: " + String(v));
delay(300);
}
- pinMode(pin, mode)
- AD変換に使用するGPIOピンを指定します。
- GPIO36を使用する場合 ー> pinMode(36, ANALOG);
- analogSetAttenuation(ADC_11db)
- この関数で減衰器を指定します。
- 今回は最大減衰のADC_11dbを使用します。測定範囲は0〜3903mV。
- analogRead(pin)
- この関数でAD変換された値を読み込む事が出来ます。
- ESP32の場合は 12ビットの変換になります。
- 最小0V: 0 / 最大3.9V:4096(12ビット)
測定結果
このプログラムを実行したところ
- 最大値を3900mVとして 12ビットでの解像度は 3900 / 4090 ≒ 0.95 となる。
- 戻り値は 2749。解像度を掛けて電圧を求めると 2749 x 3900 / 4090 ≒ 2617mV
- 実際に GPIO36 の電圧をテスタで測定すると2360mV。
戻り値と実際の測定値とで誤差が有ります。誤差の原因として
- ESP32が持っている基準電圧 1100mVに誤差がある
- 製品を製造する場合製造誤差は当然生じる
- 基準電圧は測定値に影響する。
- 最大測定値 3.9V を 4095 とする事に無理がある。
- 自身の電圧が3.3Vに対して 0から3.9Vの測定範囲に無理がある。
- 3.3v 以上の測定に無理が有るとすると 3.9V -> 4096 とするのは正しく無いのでは
補正が必要
測定値を返り値から計算した電圧で割る、2360 / ( 2749 / 4096 X 3900 ) = 0.9
これを補正係数とする。これを戻り値に掛けて(補正して)V2を求めてみた。
抵抗 R1,R2 を測定したら
- R1:回路 51KΩ 実際 50kΩ
- R2:回路 220kΩ 実際 218kΩ
よって V2 は V2 = 2749 x 0.9 x 3900 / 4096 / 50 x (50 + 218) = 12626mV。 実際に V2 を測定したら12690mVでした。かなり正確です。
補正を自動で行う関数
ESP32は内部に基本電圧(1100mV) に対する補正値を持っています。
これを使えば正確な AD変換が出来るはずです。Arduinoで使用出来るESP-IDE関連の関数に
esp_err_t esp_adc_cal_get_voltage(adc_channel_t channel, const esp_adc_cal_characteristics_t *chars, uint32_t *voltage)
が有ります。この関数は AD変換された値に補正値を適応してくれます。
この関数の使用は、 “driver/adc.h” と “esp_adc_cal.h” を includeし下記の設定を行います。
- esp_err_t adc1_config_width(adc_bits_width_t width_bit)
- AD変換の解像度の指定
- esp_err_t adc1_config_channel_atten(adc1_channel_t channel, adc_atten_t atten)
- ADCに使用する GPIOピンと減衰器の指定
- esp_adc_cal_value_t esp_adc_cal_characterize(adc_unit_t adc_num, adc_atten_t atten, adc_bits_width_t bit_width, uint32_t default_vref, esp_adc_cal_characteristics_t *chars)
- 補正計算に必要なパラメータを指定する関数
- 引数
- 第1: adc_num。ADC_UNIT_1を使用。
- 第2: 減衰器の指定
- 第3: 解像度の指定
- 第4: ESP32が自身の基準電圧を持っていない場合使用する基準電圧
- 第5: パラメータ保存用のポインタ
これらの関数を使った測定用のプログラムは以下の通り。
#include "driver/adc.h"
#include "esp_adc_cal.h"
void setup(){
Serial.begin(115200);
}
void loop(){
uint32_t voltage,vol;
int a;
esp_adc_cal_characteristics_t adcChar;
vol = 0;
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adcChar);
for(a = 0; a < 10; a++){
esp_adc_cal_get_voltage(ADC_CHANNEL_0, &adcChar, &voltage);
vol += voltage;
delay(50);
}
voltage = vol / 10;
vol = voltage * 268 / 50;
Serial.println("V1: " + String(voltage) + "[mV] / V2: " + String(vol) + "[mV]");
delay(300);
}
- 14行:解像度を指定
- 15行:ADC用のピンと減衰器を指定
- adc1で使えるGPIOピンは以下の通り
- 今回はGPIO36を使用するので、ADC1_CHANNEL_0。
定数 GPIOピン ADC1_CHANNEL_0 36 ADC1_CHANNEL_3 39 ADC1_CHANNEL_4 32 ADC1_CHANNEL_5 33 ADC1_CHANNEL_6 34 ADC1_CHANNEL_7 35 - 16行:計算の前準備
- 18から24行:10回測定してその平均を取る
- 19行:ここでADCを実行。
- esp_adc_cal_get_voltage()は測定値を電圧(mV)で返してくる
- 25行:V1からV2を計算
- 26行:値を表示
プログラムを実行し表示される値と実際に測定した値を比較してみました。
- V1:プログラム 2373mV 実際 2354mV
- V2:プログラム 12719mV 実際 12602mV
測定値から求めた補正値を使用していませんが、補正された値が実際の値にかなり近い事が分かります。
チップのバージョンが2以上なら
簡単に補正を自動で行う関数が有る
その関数は analogReadMilliVolts() です。analogRead(pin)との比較は下記の通り
| 項目 | analogReadMilliVolts() | analogRead(pin) |
|---|---|---|
| ポートの設定 | 必要無し | 必要 pinMode(xx, ANALOG); |
| 減衰値 の設定 | 必要無し。ADC_11dbがデフォルト それ以外は analogSetAttenuation(xxxx);で設定 | 必要 analogSetAttenuation(xxxx); |
| 戻り値 | 電圧値(mV) | 12ビット(0から4096)の数字 |
| 補正 | 有り | 無し |
| Version | 2以上 | 1以上 |
測定値を補正値込で計算し電圧値(単位 mV)で値を返してくるのが特徴です。 測定レンジは ADC_11db( 0 から 3.9V)がデフォルトですがレンジの変更も可。 Version 2以上ならこれ一択です。下記は測定例のスケッチ。測定値は 2361mVでした。
void setup() {
int a;
uint32_t mv;
Serial.begin(115200);
delay(1000);
// ピンモードの設定(アナログ入力)省略可
pinMode(36, INPUT);
for(a = 0; a < 10; a ++)
mv += analogReadMilliVolts(36);
mv /= 10;
Serial.print(mv);
Serial.println(" mV");
}
void loop() {
delay(500);
}
最後に
analogRead(pin) だけでも十分な気がしますがVersion 2以上なら迷わず analogReadMilliVolts() です。 解像度やリニアリティを問題にしているのを見かけますが、個人的にはこれで十分です。