2020/05/19 17:58:eFuse Vrefが記録されてない場合について追記しました(動作未確認)
概要
Arduino IDEを使って、ESP32独自のAPIでADCから正確な電圧を取る方法をまとめています。
ESP32のADCについてググるとかなり多くの記事がヒットするんですが、記事執筆時点では正しかったであろうけれど、現在では正しくない情報が多くて苦労しました。一応正解と思しき情報に行き当たり、ADCから正確に電圧を測定することができたので、自分用のメモも兼ねてまとめておきます。
多くの記事でArduinoの関数であるanalogReadを使ってADCの生の値を取得し、なんやかんや頑張って電圧値に変換しようとしていますが、そんなことをしなくても正確な電圧値を返してくれるESP32のAPIがあって、現在はArduino IDEからも使うことができます。
当方の環境
- Arduino IDE
- Pythonおよびpipがインストール済みのWindows
参考にしたHP
まあ正解は公式ページに書いてある通りなんで、英語読めってことなんですけどね
ESPRESSIF公式
https://docs.espressif.com/projects/esp-idf/en/stable/api-reference/peripherals/adc.html
ESP32マウスPart.31 ADCでバッテリ電圧を計測
https://rt-net.jp/mobility/archives/9839
ESP32でesptool.pyの使い方
https://lang-ship.com/blog/work/esp32-esptool-py/
Calibration済の個体かどうか確認する
Per design the ADC reference voltage is 1100mV, however the true reference voltage can range from 1000mV to 1200mV amongst different ESP32s.
って感じで上記公式ページに記載されていて、要するにESP32のADCのリファレンス電圧(Vref)は1100mVで設計されているけれど、1000~1200mvの範囲で個体差がありますと。
Correcting ADC readings using this API involves characterizing one of the ADCs at a given attenuation to obtain a characteristics curve (ADC-Voltage curve) that takes into account the difference in ADC reference voltage. The characteristics curve is in the form of y = coeff_a * x + coeff_b and is used to convert ADC readings to voltages in mV. Calculation of the characteristics curve is based on calibration values which can be stored in eFuse or provided by the user.
んでもって1次方程式でADCの値を電圧に変換しますよと。
Calibration values are used to generate characteristic curves that account for the unique ADC reference voltage of a particular ESP32. There are currently three sources of calibration values. The availability of these calibration values will depend on the type and production date of the ESP32 chip/module.
- Two Point values represent each of the ADCs’ readings at 150mV and 850mV. To obtain more accurate calibration results these values should be measured by user and burned into eFuse BLOCK3.
- eFuse Vref represents the true ADC reference voltage. This value is measured and burned into eFuse BLOCK0 during factory calibration.
-
Default Vref is an estimate of the ADC reference voltage provided by the user as a parameter during characterization. If Two Point or eFuse Vref values are unavailable, Default Vref will be used.
Individual measurement and burning of the eFuse Vref has been applied to ESP32-D0WD and ESP32-D0WDQ6 chips produced on/after the 1st week of 2018. Such chips may be recognized by date codes on/later than 012018 (see Line 4 on figure below).
そして、この1次方程式を決定するために、①Two Point, ②eFuse Vref, ③Default Vrefの3つのソースが用意されています。①が最も正確ですがユーザーがeFuseに書き込む必要があり、ハードル高いので試していません。~~③はVrefの設計値である1100mVを使うので個体差は考慮されません。~~③は後述する関数esp_adc_cal_characterize
で与える第4引数のことですね。この記事は主に②について解説しています。
②のeFuse Vrefとは、個体ごとに測定されてチップに記録されているリファレンス電圧です。これを使えば個体差を考慮した上である程度正確に電圧が取れます。ただし、これがチップに記録されているのは2018年第1週以降に製造された個体です。正真正銘の裸チップであればシリアルナンバーから製造日時が分かるようですが、だいたいの人は技適を通すためにシールドされた製品を使っていると思います。したがって、以下で説明するesptoolを使ってeFuse Vrefが記録されている個体か確かめましょう。
esptoolのインストール
当方はWindows環境なのでPowerShellを立ち上げて
pip install esptool
Pythonとかはあらかじめ入れておく必要があります。
ソースコード:https://github.com/espressif/esptool
espefuse.pyでeFuse Vrefをチェック
ESP32をPCに接続した上で
espefuse --port COM*** adc_info
(***
はESP32を接続しているCOMの番号です。Arduino IDEとかデバイスマネージャーとかで調べましょう。MacとかLinuxなら/dev/ttyUSB***
って感じになるのかな。)
そうすると、eFuse Vrefが記録されている個体なら
ADC VRef calibration: 1135mV
という感じでその値が返されます。私の個体は1135mVでした。この値を自分で記録しておく必要はありません。eFuse Vrefがチップに記録されていればAPIがよしなにやってくれます。
もしeFuse Vrefが記録されていない個体なら
ADC VRef calibration: None (1100mV nominal)
という感じで返ってきます。この場合は自分でVrefを計測して書き込む必要があり、そのためのAPIも用意されているようですが、当方はやっていないのでこの記事では割愛します。 上にも追記しましたが、esp_adc_cal_characterize
の第4引数で与えればいいようです。計測は多分adc2_vref_to_gpio
を使うのかな。(未確認)
ESP32のAPIで電圧値を取得
// ESP32のADCライブラリをインクルード
#include "driver/adc.h"
#include "esp_adc_cal.h"
esp_adc_cal_characteristics_t adcChar;
void setup(){
Serial.begin(115200);
// ADCを起動(ほかの部分で明示的にOFFにしてなければなくても大丈夫)
adc_power_on();
// ADC1_CH6を初期化
adc_gpio_init(ADC_UNIT_1, ADC_CHANNEL_6);
// ADC1の解像度を12bit(0~4095)に設定
adc1_config_width(ADC_WIDTH_BIT_12);
// ADC1の減衰を11dBに設定
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
// 電圧値に変換するための情報をaddCharに格納
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adcChar);
}
void loop(){
uint32_t voltage;
// ADC1_CH6の電圧値を取得
esp_adc_cal_get_voltage(ADC_CHANNEL_6, &adcChar, &voltage);
Serial.println("Voltage: " + String(voltage) + "[mV]");
delay(300);
}
ADC1_CH6(GPIO34)の電圧値を300ミリ秒ごとに読み取って、シリアルモニタに表示するサンプルです。安物テスターでの計測値と比較すると10~20mVほど高く出ていましたが、テスター端子の接触抵抗を考えると正確な値が出ていると考えていいと思います。
以下、重要な部分を解説していきます。
// ADC1_CH6(GPIO34)を初期化
adc_gpio_init(ADC_UNIT_1, ADC_CHANNEL_6);
ADCのチャンネルとピンの対応関係はデータシートに載ってます。私はいつもSwitch Scienceさんのこのページ(https://trac.switch-science.com/wiki/esp32_tips )を見ています。(ただし、このページのADCに関する解説は正しくありません。)
// ADC1の解像度を12bit(0~4095)に設定
adc1_config_width(ADC_WIDTH_BIT_12);
ただし、デフォルトで12bitなので明示的に設定しなくても大丈夫ではあります。
// ADC1の減衰を11dBに設定
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
以下めっちゃ重要です。これに言及している記事がほとんどなくて困ってました。
The default ADC full-scale voltage is 1.1V. To read higher voltages (up to the pin maximum voltage, usually 3.3V) requires setting >0dB signal attenuation for that ADC channel.
When VDD_A is 3.3V:
0dB attenuaton (ADC_ATTEN_DB_0) gives full-scale voltage 1.1V
2.5dB attenuation (ADC_ATTEN_DB_2_5) gives full-scale voltage 1.5V
6dB attenuation (ADC_ATTEN_DB_6) gives full-scale voltage 2.2V
11dB attenuation (ADC_ATTEN_DB_11) gives full-scale voltage 3.9V (see note below)
Due to ADC characteristics, most accurate results are obtained within the following approximate voltage ranges
At 11dB attenuation the maximum voltage is limited by VDD_A, not the full scale voltage.
0dB attenuaton (ADC_ATTEN_DB_0) between 100 and 950mV
2.5dB attenuation (ADC_ATTEN_DB_2_5) between 100 and 1250mV
6dB attenuation (ADC_ATTEN_DB_6) between 150 to 1750mV
11dB attenuation (ADC_ATTEN_DB_11) between 150 to 2450mV
For maximum accuracy, use the ADC calibration APIs and measure voltages within these recommended ranges.
上記は公式からの引用です。今回の実装で必要になる情報を抜き出して訳すと
- オリジナルのADCのfull-scaleは1.1Vなので、それ以上を計りたければ減衰を設定しましょう
- Vddが3.3Vのとき、11dBの減衰ならfull-scaleは3.9V
- ただし、Vdd=3.3V, 11dB減衰で正確に測れるのは150~2450mVの範囲
3つ目がメチャクチャ重要です。測りたい電圧がこの範囲に収まるように、適切に分圧しましょう。
// 電圧値に変換するための情報をadcCharに格納
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adcChar);
ADC1を減衰11dB、解像度12bitに設定してますよという情報をadcChar
に格納。このadcChar
はあとで使います。APIで独自に定義されている構造体で、電圧値算出用の1次式の係数なども格納されます。
4つ目の引数はeFuse Vrefが記録されていない場合に使うVrefの値を指定しているようです。1100mVを指定していますが、eFuse Vrefを使うので関係ありません。
// ADC1_CH6の電圧値を取得
esp_adc_cal_get_voltage(ADC_CHANNEL_6, &adcChar, &voltage);
ADCの設定情報やeFuse Vrefなどを参照した上でADCの生の値をよしなに処理し、mV単位の電圧値としてvoltage
に格納します。voltage
の型はuint32_t
です。adcChar
のアドレスを渡すことで、ADCの設定情報を与えています。
第1引数がADC1_CHANNEL_6
ではなく、ADC_CHANNEL_6
でないといけない理由は正直よくわかりません。ADC2とどうやって区別してるんだろう...
ADC1 or 2の指定は、adcCharによって与えられます。
戻り値とか気になる人は公式のリファレンスを読んでください。だいたいはesp_err_t
でESP_OK
なら成功って感じです。
その他の便利な関数
公式リファレンスに全部書いてありますが、一部便利そうなのを紹介します。(動作確認してません)
// 生の値を取得
// ADC1とADC2で関数のカタチが違うので注意
int adc1_get_raw(adc1_channel_t channel)
esp_err_t adc2_get_raw(adc2_channel_t channel, adc_bits_width_t width_bit, int *raw_out)
// ADCをULP Coprocessorからアクセス可能にする
// ULP = Ultra Low Power
void adc1_ulp_enable()
// GPIOにADC2のリファレンス電圧を出力
// これを計測すればeFuse Vrefが正しいか確かめられると思われる
esp_err_t adc2_vref_to_gpio(gpio_num_t gpio)
// eFuse Vrefが設定されているか確かめる
// これを使えばesptoolいらないのかも
esp_err_t esp_adc_cal_check_efuse(esp_adc_cal_value_t value_type)
// ADCの生の値を電圧値に変換する
uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, constesp_adc_cal_characteristics_t *chars)