ESP32のADCと分圧抵抗、熱電対で温度(240℃くらい)を測定する

# はじめに
ESP32で240℃程度のホットプレートの温度を測定したかったので試行した内容をメモ書き。
※開発環境はArduinoIDEを利用

# 目次
1. 測定方法の選定と試行
1. Pt100の抵抗-温度の近似式を作成
1. 分圧抵抗の選定
1. 測定回路
1. 測定プログラム
1. 実測
1. まとめ

## 1. 測定方法の選定
今回、選定にあたり以下の要件を定めた

- 測定したい対象は最大270℃程度まで上昇する。
- 測定誤差は240℃で±10℃くらいを狙う。
- Arduino(ESP32)で利用できる。
- できるだけ低コスト

### (1) ＮＴサーミスタ（高感度高耐熱サーミスタ) + 分圧抵抗【失敗】
https://akizukidenshi.com/catalog/g/gP-11896/

まず始めに上記NTサーミスタを購入、テストしたが簡単に選定理由とテスト結果を書く。

- 選定理由
    - 応答が早く、高精度な制御ができそう。
    - 耐熱温度が300℃あり、今回の要件では余裕がある。
    - 精度が高い(±3%)、240℃で±7℃程度の誤差で済む。
- 懸念事項
    - 配線加工が面倒
    - ガラス製なので急激な温度変化で割れそう
    - 温度に対して抵抗値の変化量が少ないので、測定誤差が心配
- 試行結果
    - 配線加工必要になる耐熱線と耐熱保護のためのガラス繊維チューブが高かった・・・・。
    - 素早い応答性で測定対象の温度にリアルタイムで追従するのがとても良い。
    - 温度が上がれば上がるほど、測定結果に誤差が出た。
    - 総コスト2000～3000円くらい・・・。
- 考察
    - 高温域の抵抗値の変化が0.01単位であるため、テキトーな回路では測定誤差が大きく、測定結果が大幅にずれてしまった。(対象240℃で結果193℃と表示された。)
    - コスト的にもよろしくない。

### (2) 白金測温抵抗体(Pt100) + MAX31865【失敗】
https://www.amazon.co.jp/gp/product/B07Q2NGTSF/

https://www.amazon.co.jp/gp/product/B093D2Q4KJ/

次に、よく温度測定で使用される熱電対と熱電対用の測定モジュールを購入、簡単に選定理由とテスト結果を書く。

- 選定理由
    - 抵抗値が温度に比例して変化するため、とても扱いやすい。
    - 温度に対して抵抗値の変化量が大きいため、多少の誤差は測定結果に影響が少ない。
    - 420℃まで測定できる。(らしい)
    - 専用モジュールを使えばプログラムを簡素化できるし、誤差を気にしなくてよくなる。
- 懸念事項
    - 中華製なので信頼性がわからない
    - 温度変化に対する応答性が低そう
- 試行結果
    - 今回購入した中華製の熱電対はPt100の規格に沿っており、問題なかった。(規格: https://netsushin.co.jp/img/product/pt100.pdf )
        - 沸騰したお湯(100℃)に投入⇒抵抗値138Ωで◎ 
    - 応答性は低いが、問題ない程度。 
    - MAX31865を公式リファレンスに従い使用してみたが、動作せず。
        - 出力結果： 0.00 Ohms, -256.00 C, RTD Low Threshold Met,
- 考察
    - 海外フォーラムでAdafruit製ではない中華製のBreakout基板はヒューズを実装する箇所になぜかコンデンサが実装されており、動かないという記述を発見。確かに基板を確認するとコンデンサがついているように見える・・。公式基板を購入しようと思ったが5000円近くするので断念。

### (3) 白金測温抵抗体(Pt100) + 分圧抵抗【成功】
最後に、白金測温抵抗体の特性から普通に分圧抵抗で計測できるんじゃないかと思い、試したところ思った通りの結果が得られた。
次項以降で記述。

## 2. Pt100の抵抗-温度の近似式を作成
測定方法が決まったので、次にプログラムで使用する抵抗値から温度を算出する近似式を作成する。
(よく配列で温度を出すプログラムを書いている人がいるが、メモリ消費が激しすぎるのでお勧めしない・・・）

まず、Pt100の規準抵抗値表↓から使用する温度範囲を抜粋し、Excelに貼る。
この時、使用する範囲を絞ったほうが正確な近似式が作成できる。

https://netsushin.co.jp/img/product/pt100.pdf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/8392c820-fc23-27a3-7b2a-9d16531a1318.png)
次に、グラフにするため温度と抵抗値を2列にする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/8e2e5566-2dc0-62c0-af27-ad1950b15b1f.png)
グラフで散布図にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/8f3c2684-3bb0-f40f-cfe6-50f19f8242b7.png)
グラフのデータ点を右クリックして『近似曲線の追加』をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/6fe0eb79-365c-26ef-970b-d15491d362c5.png)
右のメニューにある『グラフに数式を追加する』に✓を付けると、グラフに数式が作成される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/9301d4d7-56b5-5b59-aa8e-f68f84180045.png)

今回は『温度(℃) = 2.6647 * Pt100の抵抗値 - 268.32』の数式を得ることができた。

## 3. 分圧抵抗の選定
今回、プログラム内ではESP32のADCを利用します。
ADCはアッテネータの減衰量によって正確に測定できる電圧範囲が変化するため、公式マニュアルを参考に最適な減衰量を決めます。

> 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

今回は11dbの減衰量で使用します。
減衰量11dbで使用する場合、計測する電圧は0.15(V)から2.45Vの間で使用しなければならず、分圧抵抗による電圧出力がこの範囲で収まるように調整しなければなりません。

今回、熱電対は0℃～270℃の範囲で使用しますが、この時の抵抗値は特性表から100Ω～200Ω程度であることが想定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/33a00691-0a30-d0e4-1a3d-e6167f727263.png)
分圧回路で熱電対がR1として、今回はR2を選定します。
R1が100～200Ωの際に、VoutがADCの測定範囲である0.15V以上2.45V以下となるR2の抵抗値を探します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/14c37f0e-e3d7-4747-82a3-de0a6d6b7b1b.png)


今回は分圧抵抗の逆算ツールを使用します。

https://www.petitmonte.com/robot/teikou_bunatu_gyakusan.html

R2が許容できる抵抗器の最大値を計算するためには

- 入力電圧を3.3V
- R1に熱電対で想定される最小値100Ω
- 出力電圧を2.45V

をパラメータとして計算します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/ced064a7-673e-a144-526d-6c9675b8ef82.png)

R2が許容できる抵抗器の最小値を計算するためには

- 入力電圧を3.3V
- R1は熱電対で想定される最大値200Ω
- 出力電圧を0.15V

をパラメータとして計算します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/6affc375-049f-aa8c-7b97-54b9b314c052.png)

以上から、分圧抵抗R2は10Ω～288Ωの範囲で設定すればよいとわかりました。
なお、抵抗の精度も測定に大きな影響があるので、誤差が1%以下の抵抗を使います。

今回は手元に200Ω(1%)の抵抗があるので、それを使いました。

## 4. 測定回路
次に使用した回路を明示します。
ESP32は3.3V、GND、GPIO32の３つのピンを使用しています。

注意点としてESP32の3.3V出力は実測で3.3V出力されていない点を考慮する必要があります。(私の場合は3.2Vでした）
0.1Vの差ではありますが、今回のプログラムでは100℃の対象を計測した際、熱電対の抵抗値が10Ω多く出力されてしまうため+25℃の誤差で出力されてしまう問題があります。
そのため、熱電対へ供給する電圧は3.3V一定である必要があります。

今回、3.3Vを一定で供給するために秋月電子のAE-XCL102D333CR-Gを利用しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/e368c64b-5f2b-742b-b289-2cf089fcc0a2.png)

## 5. 測定プログラム
```C
#include <Arduino.h>
#include <driver/adc.h>
#include <esp_adc_cal.h>

#define ONDO_PIN 32 //分圧抵抗のVoutを計測するピン
#define R2 200.0 //分圧抵抗R2の抵抗値
#define Vcc 3300 //入力電圧をmVで
#define Ct 100.0 //平均値の母数

hw_timer_t * timer = NULL; //定期実行用タイマー
esp_adc_cal_characteristics_t *adc_chars_1; //ADC

void setup() {
  Serial.begin(115200);

  pinMode(ONDO_PIN, ANALOG);
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_GPIO32_CHANNEL, ADC_ATTEN_DB_11);
  adc_chars_1 = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t)); //  ADC1

  esp_adc_cal_value_t val_type_1 = esp_adc_cal_characterize(
                                     ADC_UNIT_1,      // adc_unit_t ADCユニット番号
                                     ADC_ATTEN_DB_11,      // adc_atten_t アッテネータ設定値
                                     ADC_WIDTH_BIT_12,    // adc_bits_width_t ADC解像度
                                     ADC1_GPIO32_CHANNEL, // uint32_t デフォルト校正値
                                     adc_chars_1); // esp_adc_cal_characteristics_t * キャリブレーション用の構造体へのポインタ

  start_heat();//計測開始
}

void loop() {
}

// Pt100の抵抗値から温度を算出する
void getTemp() {
  uint32_t raw = 0;
  for (int cnt = 0; cnt < Ct ; cnt++) {
    raw = raw + adc1_get_raw((adc1_channel_t)ADC1_GPIO32_CHANNEL);//ADCの計測値を取得
  }
  if (raw > 0)
  {
    Serial.println("-");
    uint32_t voltage = esp_adc_cal_raw_to_voltage(raw/Ct, adc_chars_1); // ESPのキャリブレーション値から正確な電圧を取得
    Serial.print(voltage);
    Serial.println("(mV)");

    double r1 = ((Vcc - voltage) / (double)voltage) * R2; // R1の抵抗値を計算
    Serial.print(r1);
    Serial.println("(Ω)");

    double temp = (2.6647 * r1) - 268.32; //近似式から温度を計算
    Serial.print(temp);
    Serial.println("(℃)");
  }
  else
  {
    Serial.println("-- ADC1 is No Signal. --");
  }
}

void IRAM_ATTR onTimer() {
  getTemp();
}

void start_heat()
{
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 1000000, true);//1.0sec
  timerAlarmEnable(timer);
}
```


## 6. 実測
室温23.3℃でこのような結果になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/1f922b02-10fe-a64a-10ba-d57616899269.png)


数℃誤差がありますが、近似式の弊害です。
109.13Ωは特性表では大体23℃+αで室温とほぼ同じですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/423799/f634776e-32df-a5d9-ccef-6a9163302a44.png)

## 7.まとめ
今回はESP32でADCを使用して温度測定を実装しました。
当初想定していた方法より、低コストかつ正確な値を取得できましたので満足です。

また、今回初めてQiitaで記事を書きましたが、突っ込みどころがあればご指摘いただけると幸いです。