A/D 変換 (ESP-IDF 環境 + C 言語) [1,2 班]

プログラムの書き方

A/D 変換のプログラムの書き方は, ESP-IDF Programming GuideAPI ReferenceAnalog to Digital Converter (ADC) Oneshot Mode Driver を参照して欲しい. また, プログラムを書く上でSWITCH SCIENCE の ESP-WROOM-32に関するTIPSの ADC の項目にも目を通しておくと良い. AD 変換する電圧の範囲や解像度についてよくまとめられている.

A/D 変換を用いる場合も,PWM と同様に,分解能を設定する必要がある.アナログセンサの出力電力はアナログ値 (連続値) であるのに対して, マイコンが扱えるのはデジタル値 (離散値) だからである.以下の例は 2 bit で変換した場合であるが,分解能を上げれば元のアナログ値に近づく.

以下に最低限使う必要のある関数を挙げる. 気をつけねばならないことは, ESP32 マイコンは 2 つの A/D 変換の「ユニット」をサポートしていることである. AD1 (GPIO 32 ~ 39) と AD2 (GPIO 0, 2, 4, 12~15, 25~27) では初期化の関数が異なる. また,プログラム中では機器の接続されているピン番号ではなく「チャンネル」を指定することになる. チャンネルの番号はピンによって決まっているので, ESP32 のピンアウトを参照してほしい.例えば GPIO 39 は unit 1, channel 3 となる.

//ユニットの設定
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {
   .unit_id = unit,                                  //ユニットの番号
};
adc_oneshot_new_unit(&init_config1, &adc1_handle);

adc_oneshot_chan_cfg_t config = {
   .bitwidth = ADC_BITWIDTH_DEFAULT,                 //バンド幅
   .atten = atten,                                   //分解能の設定
};
adc_oneshot_config_channel(adc1_handle, channel, &config);  //チャンネルに分解能の情報を紐づけ

//値を読む
adc_oneshot_read(adc1_handle, channel, &adc_raw);

上記の adc_oneshot_read で得られる数字は,電圧そのものではなく,分解能に応じた数字 となっている.得られた数字を電圧に変換するためにはキャリブレーション用の関数を利用する. キャリブレーションのやり方はいくつか容易されているようだが,ここでは最も単純な Line Fitting を用いている.

// 設定
adc_cali_line_fitting_config_t cali_config = {
   .unit_id = unit,                    // ユニット
   .atten = atten,                     // 分解能
   .bitwidth = ADC_BITWIDTH_DEFAULT,
   .default_vref = 1100,               // マニュアルによると "ADC reference voltage is 1100mV" とある.
};
adc_cali_handle_t cali_handle = NULL;
adc_cali_create_scheme_line_fitting(&cali_config, &cali_handle);

// 生データを電圧に変換
adc_cali_raw_to_voltage(cali_handle, adc_raw, &voltage);

例題:サーミスタ

サーミスタとは

サーミスタは温度によって抵抗値が変化する素子である. 教育ボードでは以下のような回路が組まれており, Vref を測定できるようになっている. Vref よりサーミスタの抵抗値を計算で得れば,抵抗値より温度を決めることができる (温度の計算については実験課題で取り扱う).

プロジェクトの準備

サンプルプロジェクトをコピーする.

$ cd ~/esp

$ cp -r esp-idf/examples/get-started/sample_project ./adc

$ cd adc

プログラムの作成

このサンプルプロジェクトのメインファイルは main ディレクトリ以下の main.c であるので, そのファイルを編集する.エディタとして,vi, emacs, gedit, code などが使える.

#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"

static const char          *TAG    = "ADC";
static const adc_channel_t channel = ADC_CHANNEL_3;    // GPIO 39
static const adc_atten_t   atten   = ADC_ATTEN_DB_12;  // 分解能
static const adc_unit_t    unit    = ADC_UNIT_1;       // ユニット

void app_main(void)
{
   //-------------ADC1 Init---------------//
   adc_oneshot_unit_handle_t adc1_handle;
   adc_oneshot_unit_init_cfg_t init_config1 = {
      .unit_id = unit,                              //ユニット
   };
   adc_oneshot_new_unit(&init_config1, &adc1_handle);

   adc_oneshot_chan_cfg_t config = {
      .bitwidth = ADC_BITWIDTH_DEFAULT,
      .atten = atten,                               //分解能
   };
   adc_oneshot_config_channel(adc1_handle, channel, &config); //チャンネルへの紐づけ

   //-------------ADC1 Calibration Init---------------//
   adc_cali_line_fitting_config_t cali_config = {
      .unit_id = unit,                              //ユニット
      .atten = atten,                               //分解能
      .bitwidth = ADC_BITWIDTH_DEFAULT,             
      .default_vref = 1100,                         
   };
   adc_cali_handle_t cali_handle = NULL;
   adc_cali_create_scheme_line_fitting(&cali_config, &cali_handle);

   //-------------ADC1 Read---------------//
   int adc_raw;
   int voltage;

   while (1) {
       // 生データの取得
       adc_oneshot_read(adc1_handle, channel, &adc_raw);
       // 生データを電圧へ変換
       adc_cali_raw_to_voltage(cali_handle, adc_raw, &voltage);
       // 出力.
       // 電圧変換へのイメージをつかむため,生データを分解能で割り算することで得られる電圧 (mV) も出力.最大値 3.6 V = 3600 mV.
       ESP_LOGI(TAG, "ADC Channel[%d] Raw Data: %d, Cali Voltage: %d (%4.1f) mV", channel, adc_raw, voltage, adc_raw / 4096.0 * 3600);
       //1秒待つ
       vTaskDelay(pdMS_TO_TICKS(1000));
   }
}

ビルドとマイコンへの書き込み

idf.py build コマンドを実行する.

$ idf.py build

マイコンに書き込むのは idf.py flash コマンド, 標準出力を表示するのは idf.py monitor コマンドである. まとめて idf.py flash monitor としても良い.

$ idf.py flash monitor
monitor を終了するのは Ctrl-] である.

挙動の確認

以下のような出力が得られるはずである.実習基板上のサーミスタ (LED の横隣りの青い素子) を触ると,電圧が変化することを確認してほしい.

I (305) ADC: ADC Channel[3] Raw Data: 2005, Cali Voltage: 1757 (1762.2) mV
I (1305) ADC: ADC Channel[3] Raw Data: 2005, Cali Voltage: 1757 (1762.2) mV
I (2305) ADC: ADC Channel[3] Raw Data: 2004, Cali Voltage: 1756 (1761.3) mV
I (3305) ADC: ADC Channel[3] Raw Data: 2005, Cali Voltage: 1757 (1762.2) mV
I (4305) ADC: ADC Channel[3] Raw Data: 2003, Cali Voltage: 1756 (1760.4) mV

加えて,ESP32 マイコンの関数を用いて得られた電圧と, 生データを自分で計算して得た電圧が概ね近い値にあることも確認してほしい. 上記の例では 0.2 % 程度の誤差となっている.

この誤差をどう解釈するかであるが,個人的な憶測であるが, SWITCH SCIENCE の ESP-WROOM-32に関するTIPSの ADC の項目で, 誤差を小さくするためにメーカが苦心している様子が読み取れ,電圧を求めるのに用いた関数内では 単純なラインフィッティングではないのかもしれない.このあたりは本来は ESP-IDF のソースを読む必要があるが,まだ実行していない.

確認しよう

実習ボード上のサーミスタを手で触り続けると,電圧が時間をかけてある値に収束することが分かるだろう. このように,ある熱源に接したときに,サーミスタが熱源の温度に馴染むまでにある程度の時間が必要である. サーミスタを使う場合は,熱源に触れさせて計測を行うまでに多少の時間をおく必要がある. このようなサーミスタの熱的応答性の度合いを表す定数を「熱時定数」という.

実践課題

例題のプログラムを改良し,実習ボードの LCD に抵抗値と電圧を 0.5 秒間隔で表示できるようにせよ.

サーミスタの抵抗値の計算には,抵抗 (10kΩ) を流れる電流とサーミスタを流れる電流が等しいという 関係を使えば良い.

ヒント

得られた電圧や抵抗値を文字列として LCD に表示する場合には,例えば以下のようなプログラムを書けばよい.

char *buffer = (char *)malloc(10 * sizeof(char));  // 数字を文字列に変換するためのバッファを動的に割り当て.

while (1){

   lcd_clear();
   sprintf(buffer, "%d", voltage);     // 整数を文字列に変換
   lcd_home1();
   lcd_print(buffer, 4);  // 文字列を表示

}

レポート課題 5: サーミスタ温度計

実施方法

[1] 目的:

  • 温度計として一般的に使われるサーミスタの原理について理解する.温度によってサーミスタの抵抗値が変化することを調べる.

[2] 使用する機器:

  • マイコンボード
  • サーミスタ
  • 温度計
  • ホットプレート
  • かきまぜ棒

[3] 実験の方法: 赤を 3.3V, 黒を GND につないでください.黄色を GPIO 36 (ADC_UNIT_1, ADC_CHANNEL_0) につないで下さい.

[4] 使用するプログラム:

  • 実践課題で作成したプログラムを利用すること.

レポートのまとめ方

以下の内容を記述すること.

  • 実験の目的
  • 実験方法
    • 利用する機器の一覧
    • 回路図
    • 温度計測の手順を簡潔にまとめて記す
  • 実験結果
    • 温度計で測定した温度と,その時にマイコンに表示された電圧と抵抗値を表形式で示す.
    • サーミスタの電気抵抗の温度変化をグラフ表示する.横軸が温度計の示す温度,縦軸を抵抗値とすること.軸のラベルや単位は必ず付すこと.
      • グラフ用紙に手書きすること.

  • 議論
    • 実験結果から作成したグラフの特徴や,それから言えることをまとめること.
    • 抵抗値から温度を求め,温度計で測った温度との誤差を示すこと.誤差の生じた理由は複数あると思われるが,それらをまとめて論述すること.

温度への換算方法

利用するサーミスタは 25 度のときに 10 kΩ,50 度のときに4.16 kΩ となるような特性を持つ.

サーミスタは温度によって抵抗値が変化する素子である. サーミスタの抵抗値は温度の関数として以下のような近似式で表現できる.

但し, R と Rref はそれぞれサーミスタの抵抗値と基準となる抵抗の抵抗値, B はサーミスタの種類によって決まる定数, T と T0 はそれぞれ温度と基準温度, である. 上式を温度について解けば以下のように書ける.

上式中の定数の値であるが, サーミスタの紹介ページ にあるように,

  • Rref = 10.0 kΩ
  • T0 = 25 ℃
  • B = 3380

となっている. あとは上記実験より抵抗値 R の値が計測できれば温度が計算できる.