こんにちは、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機能を使う物体検出センサのプログラムについて書きます。






