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 Guide の API Reference の GPIO & 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 を点灯させてみよ. (サーミスタ温度計を触ると)