こんにちは、shotaです。
前回の記事ではESP-IDFのADCサンプルを動かし、コードを読み解きました。
今回はADC機能を使って、ESP32マウスEspecialのバッテリ電圧を計測します。
Especial用のサンプルプログラムを公開しました。
EspeicalのプログラムはGitHubに公開してます。
そして今回、そのプログラムにブログで作成したサンプルプログラム(hello_world、Lチカ)を追加しました。
マウスのプログラムとは別にサンプルを用意しておくと、各機能(LEDやモータやエンコーダ)単体でデバックできます。
プログラムを書く前の下準備
プログラムを書く前に、Especialの回路図やESP32モジュールのデータシートを読み、情報をあつめます。
回路設計時に各ピンの機能を決めていますが、説明のために再度1つずつ確認していきます。
Especialの回路図を確認
まずはじめに、Especialの回路図を確認しましょう。
↓Especialの回路図はこちらです。
especial.pdf
また、電源回路についてのブログ記事はこちらです。
今回必要なところは、バッテリ電圧監視回路です。
Especialでは、R2、R3の抵抗分圧回路でバッテリ電圧を1/2にし、ESP32のSENSOR_VPピンに印加しています。
バッテリの定格電圧は3.7Vなので、定格出力時、SENSOR_VPピンには約1.85Vが印加されます。(R2、R3には1%の誤差があるため、ピッタリ1.85Vにはなりません)
プログラムを書くに当たって、バッテリ電圧1/2と、SENSOR_VPピンがキーワードです。
SENSOR_VPピンのADCチャンネルを確認
つぎに、ESP32モジュールのデータシートを読み、SENSOR_VPピンで使えるADCのチャンネルを確認します。
Pin Definitionsより、ADC1_CH0(ADC 1ユニットのチャンネル0)を使えることがわかりました。
サンプルコードを編集して、バッテリ電圧監視プログラム作成
↓前回使用したサンプルコードを編集して、Especialの環境に合わせます。
そして出来上がったサンプルコードがこちらです。
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/adc.h" #include "esp_adc_cal.h" #define DEFAULT_VREF 1100 // eFuseメモリのVrefを使うため、このデフォルト値は使用されない // 公式マニュアル: // https://docs.espressif.com/projects/esp-idf/en/stable/api-reference/peripherals/adc.html void app_main() { // ADC1_CH0はSENSOR_VPピンの機能 static const adc_unit_t unit = ADC_UNIT_1; static const adc_channel_t channel = ADC_CHANNEL_0; // 11dB減衰を設定。フルスケールレンジは3.9V // SENSOR_VPピンにはバッテリ電圧の1/2 (約1.85V)が印加される // マニュアルより、 // 6dBで正確な値が取れる電圧範囲は0.15 ~ 1.75V // 11dBで正確な値が取れる電圧範囲は0.15 ~ 2.45V // よって、11dB減衰を設定する static const adc_atten_t atten = ADC_ATTEN_DB_11; // ADCの分解能を12bit (0~4095)に設定 static const adc_bits_width_t width = ADC_WIDTH_BIT_12; // ADCのユニットとチャンネルの設定 adc1_config_width(width); adc1_config_channel_atten(channel, atten); // ADCの特性を設定 esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t)); esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars); while (1) { uint32_t adc_reading = 0; static const int NO_OF_SAMPLES = 64; // 平均値を求めるためのサンプル数 // ADCの変換結果のばらつきを抑えるため、 // 結果を複数回取得して、平均値を求める for (int i = 0; i < NO_OF_SAMPLES; i++) { adc_reading += adc1_get_raw((adc1_channel_t)channel); } adc_reading /= NO_OF_SAMPLES; // ADCの変換結果(0 ~ 4095)を電圧値 mV に変換 uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars); // ピンにはバッテリ電圧の1/2が印加されるので、voltageを2倍する uint32_t battery_voltage = voltage * 2; printf("Raw: %d\tVoltage: %dmV\tBatteryVoltage: %dmV\n", adc_reading, voltage, battery_voltage); // 1秒待機 vTaskDelay(pdMS_TO_TICKS(1000)); } }
GitHubにも公開しています。
それでは細かいところを説明します。
ADCの設定
つぎのコードブロックでADCの設定項目を決めてます。
// ADC1_CH0はSENSOR_VPピンの機能 static const adc_unit_t unit = ADC_UNIT_1; static const adc_channel_t channel = ADC_CHANNEL_0; // 11dB減衰を設定。フルスケールレンジは3.9V // SENSOR_VPピンにはバッテリ電圧の1/2 (約1.85V)が印加される // マニュアルより、 // 6dBで正確な値が取れる電圧範囲は0.15 ~ 1.75V // 11dBで正確な値が取れる電圧範囲は0.15 ~ 2.45V // よって、11dB減衰を設定する static const adc_atten_t atten = ADC_ATTEN_DB_11; // ADCの分解能を12bit (0~4095)に設定 static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
先程の説明のとおり、ADC1のCH0を使うように設定します。
減衰量は11dBとしました。
バッテリ電圧の1/2である1.85VがSENSOR_VPピンに印加されます。
この場合、0dB減衰だと、フルスケールレンジが1.1Vのためバッテリ電圧を計測できません。
公式マニュアルを参考に、最適な減衰量を決めます。
Due to ADC characteristics, most accurate results are obtained within the following approximate voltage ranges:
・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
と書かれているように、各減衰量ごとに正確に電圧を計測できる範囲が決まってます。
1.85Vを計測する場合は、6dB減衰の0.15 ~ 1.75Vでは足りません。
以上より、11dB減衰を選択します。
ADCの変換結果を取得
つぎのコードブロックで、ADCの変換結果(0 ~ 4095)を取得します。
uint32_t adc_reading = 0; static const int NO_OF_SAMPLES = 64; // 平均値を求めるためのサンプル数 // ADCの変換結果のばらつきを抑えるため、 // 結果を複数回取得して、平均値を求める for (int i = 0; i < NO_OF_SAMPLES; i++) { adc_reading += adc1_get_raw((adc1_channel_t)channel); } adc_reading /= NO_OF_SAMPLES;
サンプルプログラムと同じように、ばらつきを抑えるために、変換結果を複数回取得して平均値を求めています。
ADCの変換結果を電圧値に変換
次のコードブロックでADCの変換結果(0 ~ 4095)を電圧値(mV)に変換します。
// ADCの変換結果(0 ~ 4095)を電圧値 mV に変換 uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars); // ピンにはバッテリ電圧の1/2が印加されるので、voltageを2倍する uint32_t battery_voltage = voltage * 2; printf("Raw: %d\tVoltage: %dmV\tBatteryVoltage: %dmV\n", adc_reading, voltage, battery_voltage);
ここで、1/2されたバッテリ電圧を2倍して、もとの電圧値に戻しています。
変換結果はprintfでシリアルモニタに表示します。
プログラムのビルドと実行
それではプログラムをビルドして実行します。
cd ~/esp # GitHubからEspecialのプログラムをクローン git clone https://github.com/ShotaAk/especial.git # サンプルのプロジェクトまで移動 cd especial/examples/2_battery_checker # ビルド・書き込み・モニタ起動 make flash monitor --- 省略 --- I (291) cpu_start: Starting scheduler on PRO CPU. I (0) cpu_start: Starting scheduler on APP CPU. Raw: 2354 Voltage: 2087mV BatteryVoltage: 4174mV Raw: 2354 Voltage: 2087mV BatteryVoltage: 4174mV Raw: 2353 Voltage: 2086mV BatteryVoltage: 4172mV Raw: 2354 Voltage: 2087mV BatteryVoltage: 4174mV Raw: 2353 Voltage: 2086mV BatteryVoltage: 4172mV Raw: 2353 Voltage: 2086mV BatteryVoltage: 4172mV Raw: 2353 Voltage: 2086mV BatteryVoltage: 4172mV Raw: 2354 Voltage: 2087mV BatteryVoltage: 4174mV
無事に電圧が表示されました。
が、しかし、バッテリ電圧が4.17 Vと表示されています。
満充電された定格3.7Vのバッテリを接続したので、4.0V以上と出てもおかしくは無いのですが、、、正しく計測できたのか不安です。
正しく電圧を測定できたのか確認
そこで、安定化電源から電圧を供給して、正しく計測できたのか確認します。
計測結果がこちらです。
供給電圧 -> シリアルモニタの出力 4.10 V -> Raw: 2303 Voltage: 2045mV BatteryVoltage: 4090mV 3.90 V -> Raw: 2184 Voltage: 1946mV BatteryVoltage: 3892mV 3.80 V -> Raw: 2125 Voltage: 1898mV BatteryVoltage: 3796mV 3.70 V -> Raw: 2064 Voltage: 1847mV BatteryVoltage: 3694mV 3.60 V -> Raw: 2000 Voltage: 1794mV BatteryVoltage: 3588mV 3.50 V -> Raw: 1941 Voltage: 1746mV BatteryVoltage: 3492mV 3.40 V -> Raw: 1882 Voltage: 1697mV BatteryVoltage: 3394mV 3.30 V -> Raw: 1818 Voltage: 1644mV BatteryVoltage: 3288mV 3.20 V -> Raw: 1751 Voltage: 1589mV BatteryVoltage: 3178mV 3.10 V -> Raw: 1686 Voltage: 1535mV BatteryVoltage: 3070mV 3.00 V -> Raw: 1622 Voltage: 1482mV BatteryVoltage: 2964mV
グラフにするとつぎのとおりです。
入力電圧が低くなると誤差が増えてるように見えますが、0.04Vの誤差なのでとても小さいです。
抵抗誤差1%を考えると悪くない値です。(ESP32のADCは優秀だとは言ってません)
次回の記事
次回の記事では、同じくADC機能を使う物体検出センサのプログラムについて書きます。