A/D 変換 (ESP-IDF 環境 + C 言語)

サーミスタとは

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

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

上式中の定数の値であるが, 学習ボードでは

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

となっている. あとは抵抗値 R の値が計測できれば温度が計算できる. 抵抗値 R を計算するために, 教育ボードでは以下のような回路が組まれており, Vref の値からオームの法則より R が得られるようになっている (図はITOC のチュートリアルより引用).

Vref を求めるプログラムを書けば, Vref より R が得られる.

ESP32 マイコンには ADC(アナログ・デジタル・コンバータ)が搭載されており, Vrefの値を簡単に計測することができる.

プログラムの書き方

A/D 変換のプログラムの書き方は, ESP-IDF Programming GuideAPI ReferenceGPIO & RTC GPIO を参照して欲しい.

気をつけねばならないことは, ESP32 マイコンは 2 つの AD コンバーターをサポートしていることで, AD1 (GPIO 32 ~ 39) と AD2 (GPIO 0, 2, 4, 12~15, 25~27) では初期化の関数が異なることである. なお, AD2 と wifi は同時に使えないらしい.

また, プログラムを書く上でSWITCH SCIENCE の ESP-WROOM-32に関するTIPSの ADC の項目にも目を通しておくと良い. AD 変換する電圧の範囲や解像度についてよく書かれている.

サーミスタ温度計での計測

$ cp -r esp-idf/examples/peripherals/adc .

$ cd adc

$ cp main/adc1_example_main.c  main/adc1_example_main.c.bk

サンプルプログラム main/adc1_example_main.c の以下の行を修正する.

...
24 static const adc_channel_t channel = ADC_CHANNEL_3; 
...
28 static const adc_atten_t atten = ADC_ATTEN_DB_11;    //計測範囲 3.6V. デフォルトは 1V
...

修正した後にコンパイルとマイコンへの書き込みおよび実行を行う.

$ make

$ make flash monitor 
   ...
   eFuse Two Point: NOT supported
   eFuse Vref: NOT supported
   Characterized using Default Vref
   Raw: 1816   Voltage: 1605mV
   Raw: 1815   Voltage: 1604mV
   Raw: 1816   Voltage: 1604mV
   ...

サンプルプログラムの修正

サンプルプログラム main/adc1_example_main.c は #ifdef や ADC_UNIT 2 への対応のため, 行数が多くなっている. 必要最低限の部分だけ残して, ESP32 マイコンの API Reference を参照しながらコメントを入れると, 以下のように書ける.

1  #include <stdio.h>
2  #include "freertos/FreeRTOS.h"
3  #include "freertos/task.h"
4  #include "driver/adc.h"
5  #include "esp_adc_cal.h"
6 
7  #define DEFAULT_VREF    1100 
8  #define NO_OF_SAMPLES   64          //Multisampling
9 
10 static esp_adc_cal_characteristics_t *adc_chars;
11 static const adc_channel_t channel = ADC_CHANNEL_3; 
12 static const adc_atten_t atten = ADC_ATTEN_DB_11;
13 static const adc_unit_t unit = ADC_UNIT_1;
14 
15 void app_main(void)
16 {
17     //Configure ADC
18     adc1_config_width(ADC_WIDTH_BIT_12);
19     adc1_config_channel_atten(channel, atten);
20 
21     //Characterize ADC
22     adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
23     esp_adc_cal_characterize(unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
24 
25     //Continuously sample ADC1
26     while (1) {
27         uint32_t adc_reading = 0;
28         //Multisampling
29         for (int i = 0; i < NO_OF_SAMPLES; i++) {
30             adc_reading += adc1_get_raw((adc1_channel_t)channel);
31         }
32         adc_reading /= NO_OF_SAMPLES;
33 
34         //Convert adc_reading to voltage in mV
35         uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
36         printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
37         vTaskDelay(pdMS_TO_TICKS(1000));
38     }
39 }
  • 4,5 行目: 必要なヘッダファイルを include.
  • 7 行目: マニュアルによると "ADC reference voltage is 1100mV" とある. この値を設定しておかないといけない.
  • 11, 13 行目: サーミスタが接続しているユニット・チャンネルの設定 (GPIO 39 は UNIT 1 の ADC_CHANNEL 3 に対応)
  • 12 行目: 0 ~ 3.6 V で AD 変換するために ADC_ATTEN_DB_11 を設定.
    • SWITCH SCIENCE の ESP-WROOM-32に関するTIPSの ADC の項目に詳しい. 「ADCの入力には減衰器を設定することができます。実のところ、ESP32のADCモジュールは0~1 Vの間でAD変換を行っています。このモジュールに例えば11db(約1/3.6倍)の減衰器を設定することで、0~3.6 Vの間のAD変換ができるようになります。」
  • 18 行目: 何ビットの ADC を使うかの設定. 今回は 12 ビット.
  • 19 行目: AD1 のチャンネルの設定.
    • チャンネルとしてサーミスタの接続されている ADC_CHANNEL_3 (GPIO 39) を利用.
    • ADC_ATTEN_DB_11 を指定することで, 0 ~ 3.6V で AD 変換する.
  • 22,23 行目: Characterize an ADC at a particular attenuation.
  • 29-32 行目: 値の取得. 引数にチャンネルを指定する. ノイズ除去のために 64 回計測 (8 行目) した結果を平均している.
  • 35 行目: 電圧に変換.

修正を終えたらコンパイルとマイコンへの書き込み・プログラムの実行を行う.

$ make 

$ make flash monitor

  Raw: 1688   Voltage: 1502mV
  Raw: 1689   Voltage: 1503mV
  Raw: 1688   Voltage: 1502mV
  Raw: 1689   Voltage: 1503mV
  Raw: 1688   Voltage: 1502mV
  Raw: 1688   Voltage: 1502mV

温度計測部分の追加

これに温度計測の部分を加えると以下のようになる. log の計算をするために math.h を加えていることに注意されたい.

1  #include <stdio.h>
2  #include <math.h>
3  #include "freertos/FreeRTOS.h"
4  #include "freertos/task.h"
5  #include "driver/adc.h"
6  #include "esp_adc_cal.h"
7 
8  #define DEFAULT_VREF    1100        //Use adc2_vref_to_gpio() to obtain a better estimate
9  #define NO_OF_SAMPLES   64          //Multisampling
10 
11 static esp_adc_cal_characteristics_t *adc_chars;
12 static const adc_channel_t channel = ADC_CHANNEL_3; 
13 static const adc_atten_t atten = ADC_ATTEN_DB_11;
14 static const adc_unit_t unit = ADC_UNIT_1;
15 
16 void app_main(void)
17 {
18     //Configure ADC
19     adc1_config_width(ADC_WIDTH_BIT_12);
20     adc1_config_channel_atten(channel, atten);
21 
22     //Characterize ADC
23     adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
24     esp_adc_cal_characterize(unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_chars);
25 
26     //Continuously sample ADC1
27     while (1) {
28         uint32_t adc_reading = 0;
29         //Multisampling
30         for (int i = 0; i < NO_OF_SAMPLES; i++) {
31             adc_reading += adc1_get_raw((adc1_channel_t)channel);
32         }
33         adc_reading /= NO_OF_SAMPLES;
34 
35         //Convert adc_reading to voltage in mV
36         uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
37         printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
38 
39         // 温度計測
40         float B = 3435.0;
41         float To = 25.0;
42         float V = 3300.0 ;
43         float Rref = 10.0 ;
44         float Temp = 1.0 / ( 1.0 / B * log( (V - voltage) / (voltage/ Rref) / Rref) + 1.0 / (To + 273.0) ) - 273.0;
45         printf("Temp: %f \n", Temp);
46 
47         // wait
48         vTaskDelay(pdMS_TO_TICKS(1000));
49     }
50 }
  • 2 行目: log 計算のために math.h を追加.
  • 39-45 行目: 電圧から温度への変換. 基板の定格電圧は 3.3V なので, V = 3300 にしている.

コンパイルと実行を行うと温度が表示される.

$ make

$ make flash monitor

  .... 
  Raw: 1714        Voltage: 1523mV
  Temp: 21.065022 
  Raw: 1714        Voltage: 1523mV
  Temp: 21.065022 
  Raw: 1713        Voltage: 1522mV
  Temp: 21.034325 
  .... 

課題

LED, スイッチ, サーミスタ温度計を使ったプログラムを作成せよ.

  • 温度がある閾値を超えたら LED を点灯させてみよ. (サーミスタ温度計を触ると)

参考