前回はシリアル通信を使用して、ESP32-S3でデータを受け取る処理を動かしてみました。
今回はESP32-S3のADC機能を使って、ポテンショメータの電圧値を読み取り、シリアルモニタに出力します。
マイコンでセンサーから値を読み取る時は、ADコンバーターを使用して電圧値を読み取ることが多いので、ぜひとも習得してもらいたい単元です。
ADC(ADコンバーター)とは
ADCとは、電圧などのアナログ信号をデジタルまたはバイナリ形式(1と0で表現される形式)に変換する電子集積回路です。ESP32-S3に搭載されているADCの分解能は12ビットで、2^12=4096の細かさにアナログ値を分解できることを意味します。この数値は(3.3V電源の場合)0V~3.3Vの電圧範囲が4096等分されることを示しています。これによりアナログ値の幅がADC値と対応するようになります。ADCのビット数が多いほど、アナログ値が細かく分割され、変換結果の精度が高くなります。
この節では、ESP32-S3のADCにおけるアナログ値とデジタル値の対応関係について説明します。
1. アナログ値とデジタル値の範囲
- アナログ入力範囲:0V ~ 3.3V
- デジタル出力範囲:0 ~ 4095
2. アナログ値とデジタル値の変換
ESP32-S3のADCは、12ビットの分解能を持つため、アナログ入力範囲を4096個の等間隔なレベルに分割します。各レベルは、0 ~ 4095のデジタル値に対応します。
以下は、アナログ値とデジタル値の変換式です。
ADC 値 = アナログ電圧 / 3.3 * 4095
この式に基づいて、以下の対応関係が成り立ちます。
- アナログ値 0(0V)はデジタル値 0 に対応します。
- アナログ値 1~4095(0V + (3.3V / 4096) = 約0.000805V~3.3V)はデジタル値 1 に対応します。
3. 例
例として、アナログ値 1.65V をデジタル値に変換してみましょう。
ADC 値 = 1.65V / 3.3 * 4095 ≈ 2046
よって、アナログ値 1.65V はデジタル値 2046 に対応します。
ESP32-S3のADCについて
ESP32-S3は、12ビット逐次近似型のデジタルアナログコンバータ(ADC)を2基搭載しており、合計20本のピンを使ってアナログ信号を測定することができます。GPIOピン番号とアナログピン定義は、以下の表の通りです。
Pin number in Arduino | GPIO number | ADC channel |
---|---|---|
A0 | GPIO 1 | ADC1_CH0 |
A1 | GPIO 2 | ADC1_CH1 |
A2 | GPIO 3 | ADC1_CH2 |
A3 | GPIO 4 | ADC1_CH3 |
A4 | GPIO 5 | ADC1_CH4 |
A5 | GPIO 6 | ADC1_CH5 |
A6 | GPIO 7 | ADC1_CH6 |
A7 | GPIO 8 | ADC1_CH7 |
A8 | GPIO 9 | ADC1_CH8 |
A9 | GPIO 10 | ADC1_CH9 |
A10 | GPIO 11 | ADC2_CH0 |
A11 | GPIO 12 | ADC2_CH1 |
A12 | GPIO 13 | ADC2_CH2 |
A13 | GPIO 14 | ADC2_CH3 |
A14 | GPIO 15 | ADC2_CH4 |
A15 | GPIO 16 | ADC2_CH5 |
A16 | GPIO 17 | ADC2_CH6 |
A17 | GPIO 18 | ADC2_CH7 |
A18 | GPIO 19 | ADC2_CH8 |
A19 | GPIO 20 | ADC2_CH9 |
ESP32-S3では、コード中のアナログピン番号をGPIO番号と置き換えて使用することができます。例えばGPIO1をA0と記述できます。
ポテンショメーター(可変抵抗器)とは
ポテンショメータは、3つの端子を持つ可変抵抗です。これまでに使用してきたような抵抗は固定値ですが、ポテンショメータは抵抗値を調整することができます。ポテンショメータは、抵抗体(ワイヤやカーボン素子)と可動接触子(ブラシ)で構成されています。 ブラシが抵抗体に沿って移動すると、ポテンショメータの出力端子(3)の抵抗値(もしくは、回路内部の電圧)が変化します。下図は、線形スライドポテンショメータと、その回路図記号を示したものです。
ポテンショメータのピン1とピン2の間が抵抗体です。ピン3は可動接触子 (ブラシ) に接続されています。ブラシがピン1からピン2に向かって移動すると、ピン1とピン3の間の抵抗が直線的に抵抗値の最大値まで増加し、ピン2とピン3の間の抵抗が直線的に0まで減少します。回路において、抵抗体の両端は電源の正極と負極に接続されることが多いです。ブラシ(ピン3)をスライドさせると、電源電圧の範囲内で任意の電圧を得ることができます。
回転式ポテンショメータと線形ポテンショメータは、どちらも電圧を調整する可変抵抗器として機能しますが、唯一の違いは抵抗値の調整方法にあります。
回路図
結線図
/**********************************************************************
Filename : ADC_DAC
Description : Basic usage of ADC and DAC for esp32.
Auther : www.freenove.com
Modification: 2022/10/20
**********************************************************************/
#define PIN_ANALOG_IN 1
void setup() {
Serial.begin(115200);
}
void loop() {
int adcVal = analogRead(PIN_ANALOG_IN);
double voltage = adcVal / 4095.0 * 3.3;
Serial.printf("ADC Val: %d, \t Voltage: %.2fV\r\n", adcVal, voltage);
delay(200);
}
多くの場合はADCで読み取った値にはゆらぎがあります。抵抗を動かさなくてもADCの値は取得するたびに誤差を含んで違う値になってきます。そのため、より正確な値を必要とするようなプロジェクトではトリムミーンという計算を行うことが多いです。
トリムミーンとは
統計学における平均値の一種で、データセットの上位と下位の一定割合を除外した残りのデータの平均値を計算します。これは、異常値や極端な値の影響を受けにくい平均値として知られています。単純な平均値だと、データの上位や下位に特異値を含んでいることがあるため、中央値に近い値の平均を取るのが実務的には有用です。
トリムミーンの計算方法は以下の通りです。
- データセットをソートします。
- データセットの上位と下位の指定された割合のデータを削除します。
- 残りのデータの平均値を計算します。
例えば、以下のデータセットについて、上位10%と下位10%のデータを削除してトリムミーンを計算してみましょう。
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
- データセットをソートすると、以下のようになります。
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
- 上位10%と下位10%のデータを削除すると、以下のようになります。
2, 3, 4, 5, 6, 7, 8, 9
- 残りのデータの平均値を計算すると、5.5となります。
トリムミーンを使用するには以下のように修正してください
/**********************************************************************
Filename : ADC_DAC
Description : Basic usage of ADC and DAC for esp32.
Auther : www.freenove.com
Modification: 2022/10/20
**********************************************************************/
#define PIN_ANALOG_IN 1 // 定義を修正
double adcValues[1000]; // 変数名を修正
double voltages[1000]; // 変数名を修正
void setup() {
Serial.begin(115200);
}
void loop() {
for (int i = 0; i < 1000; i++) {
adcValues[i] = analogRead(PIN_ANALOG_IN) * 1.0; // 変数名を修正
}
double trimmedMeanADC = TrimMean(adcValues, 0.1); // 変数名を修正
double voltage = trimmedMeanADC / 4095.0 * 3.3; // 演算子を修正
Serial.printf("ADC Val: %.2f, \t Voltage: %.2fV\r\n", trimmedMeanADC, voltage);
delay(200);
}
// TrimMean関数
double TrimMean(double* data, double cutRatio) {
// データのソート
std::sort(data, data + 1000, [](int a, int b) { return a < b; }); // ラムダ式を使用
// カットするデータ数
int trimSize = int(cutRatio * 1000);
// トリムミーンの合計値
int trimmedMean = 0;
// トリムミーンの合計値を計算
for (int i = trimSize; i < 1000 - trimSize; i++) {
trimmedMean += data[i];
}
// トリムミーンの平均値を計算
trimmedMean /= (1000 - 2 * trimSize);
return trimmedMean;
}
私はトリムミーンをよく使用します
次回はタッチセンサーを使ってみましょう
コメント