2024年1月22日月曜日

stm32マイコンの内蔵温度計を使う


背景

温度情報を取得したくてマイコンのADCを調べていたところ、STM32マイコンには温度計が内蔵されているものがあると把握しました。
備忘録として使い方を記事に残します。

使ったもの

  • nucleo-l432kc
    stm32l432kcが載ったnucleoという種類で展開されている開発ボードです。
  • プログラムを書き込むPC
    ubuntu22.04で動くPCを使いました。
  • VSCode + platformio
    プログラムを書いてビルドして書き込むプログラムです。
  • USBケーブル
    nucleo-l432kcのUSBポートはmicro Bなので、microB - AのUSBケーブルを利用しました。
  • 気温計
    マイコンが測定している温度との比較しました。

stm32l432kcで温度を取得するプログラム全体像

下記の処理でstm32l432kcの内部温度計を利用して温度を取得できます。
platformio.ini
[env:nucleo_l432kc]
platform = ststm32
framework = arduino
monitor_speed = 115200
board = nucleo_l432kc
upload_protocol = mbed
build_flags =
-D ADC_SAMPLINGTIME=ADC_SAMPLETIME_640CYCLES_5

src/main.cpp
#include <Arduino.h>

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

float voltToTemperature(float voltTemp, float voltAt30C) {
return (voltTemp - voltAt30C) / 0.0025 + 30;
}

float readVoltTempInternal() {
auto adcTemp = analogRead(ATEMP);
auto adcVref = analogRead(AVREF);
const float voltVref = 1.2;
return voltVref * adcTemp / adcVref;
}

float getCal1VoltAt30C() {
auto adcTempCal1 = *TEMPSENSOR_CAL1_ADDR;
auto adcVrefCal = *VREFINT_CAL_ADDR;
return 1.2 * adcTempCal1 / adcVrefCal;
}

void loop() {
auto voltTemp = readVoltTempInternal();
auto voltAt30CDatasheet = 0.76;
auto temperature = voltToTemperature(voltTemp, voltAt30CDatasheet);
Serial.println("temperature without calibration " + String(temperature, 2) +
" C");
static const auto voltAt30CCal1 = getCal1VoltAt30C();
auto temperatureWithCal1 = voltToTemperature(voltTemp, voltAt30CCal1);
Serial.println(
"temperature with calibration: " + String(temperatureWithCal1, 2) + " C");
Serial.println("at " + String(millis()));
delay(1000);
}

実行するとこのようなログを見れます。
temperature without calibration 16.05 C
temperature with calibration: 21.61 C
at 19110

プログラムの要所を解説します。

内蔵温度計の読み取りは5us必要なのでサンプリングサイクルを変更

マイコンstm32l432kcのデータシートには内蔵温度計の読み取りには5us(マイクロ秒)が必要と記載されています。

また、本プログラムで利用する内部参照電圧も読み取りに4us必要と記載されています。

ということで、80MHzの12bit読み取り環境で4us以上を確保できるのは、設定可能最大の640.5サイクル時の約8usなので、サンプリングサイクルをその640.5に設定します。

Arduino Core STM32はADC_SAMPLINGTIMEマクロをbuild_flagに指定してサンプリングサイクルを変えれます
platformio.ini
build_flags =
-D ADC_SAMPLINGTIME=ADC_SAMPLETIME_640CYCLES_5

上記の設定で8usでアナログ読み取りを行えます。

温度センサと内部参照電圧の読み取り値を組み合わせて(補正していない)温度センサの電圧を取得

stm32の内部参照電圧は1.2Vなので、その測定値を利用して下記の処理で温度センサの電圧を算出します。
これにより、動作電圧に関係なく内部温度を算出できます。
float readVoltTempInternal() {
auto adcTemp = analogRead(ATEMP);
auto adcVref = analogRead(AVREF);
const float voltVref = 1.2;
return voltVref * adcTemp / adcVref;
}

補正情報から30℃のときの電圧を算出

stm32l432kcには30℃のとき温度センサと内部参照電圧のadc読み取り値が補正情報としてマイコンに保存されています。


Arduino Core STM32では「TEMPSENSOR_CAL1_ADDR」と「VREFINT_CAL_ADDR」でそれぞれ呼び出し可能なので、それから補正時の30℃のときの温度センサの電圧を算出します。
float getCal1VoltAt30C() {
auto adcTempCal1 = *TEMPSENSOR_CAL1_ADDR;
auto adcVrefCal = *VREFINT_CAL_ADDR;
return 1.2 * adcTempCal1 / adcVrefCal;
}

マイコンの仕様に合わせて電圧から温度を算出

stm32l432kcのセンサ情報を見ると、温度センサの誤差は±5℃、2.7mVごとに1℃変化し、30℃のときに0.76Vと分かります。
この30℃のときの電圧は、先程算出した補正情報を利用可能です。

比較として、データシートに示される30℃のときの電圧と、補正情報から算出した電圧それぞれを利用して温度センサの温度を算出しました。
float voltToTemperature(float voltTemp, float voltAt30C) {
return (voltTemp - voltAt30C) / 0.0025 + 30;
}

void loop() {
auto voltTemp = readVoltTempInternal();
auto voltAt30CDatasheet = 0.76;
auto temperature = voltToTemperature(voltTemp, voltAt30CDatasheet);
Serial.println("temperature without calibration " + String(temperature, 2) +
" C");
static const auto voltAt30CCal1 = getCal1VoltAt30C();
auto temperatureWithCal1 = voltToTemperature(voltTemp, voltAt30CCal1);
Serial.println(
"temperature with calibration: " + String(temperatureWithCal1, 2) + " C");
Serial.println("at " + String(millis()));
delay(1000);
}

余談: 精度±5℃のセンサの補正が5℃を超えていて良いだろうか

ログを見ると、今回利用したマイコンは5.6℃増える補正がされていると分かります。
temperature without calibration 16.05 C
temperature with calibration: 21.61 C
at 19110

温度センサの仕様に±5℃とあるものの、補正が5℃を超えていて良いのか疑問です。

余談: 起動後5分で約5℃温度が上がった

プログラムを書き込んで10秒ほど観察する間は、補正後の温度は気温計と1℃未満の精度で期待通りの温度です。


しかしながら、5分ほどプログラムを動かし続けると5℃以上温度が上昇し、補正前の測定値すら温度計の値を超えます。
10分経過しても同じような温度だったので、起動後約5分で発熱と放熱が均衡しました。


起動後やdeep sleep復帰後は補正後の値を信用して良さそうですが、起動し続ける場合は5℃の変化を許容するか、サーミスタを外付けするなどして温度を扱うのが良さそうです。

おわり

補正情報と内部参照電圧を利用して、stm32l432kcマイコンに内蔵されている温度計から温度情報を算出できました。
しかしながら、プログラムを動かし続けると5℃ほど測定値が上がったので、deep sleepを利用せず長時間マイコンを動かす用途では5℃の変化を許容するか外部にサーミスタなどの対応が必要そうでした。

参考

今回利用したマイコンのデータシートです。
stm32l432kc

st公式の開発のヒントページです。
内蔵温度センサ
VDDA(Vref+)の電圧値をADコンバータで測定する

以前取り組んだサンプリングレート変更方法です。
STM32Arduinoのアナログ読み取り間隔はADC_SAMPLINGTIMEマクロで変更可能

2 件のコメント :

辻田 さんのコメント...

マイコン内蔵の温度センサは、外気温ではなく、チップ上のPN接合の温度なので、差がほとんどない条件にしないと外気温としては使えないですね。

測定される電圧は、物性で決まっているので補正をかければ結構精確だと思いますよ。

Asuki Kono さんのコメント...

コメントありがとうございます

> チップ上のPN接合の温度

サーミスタを内蔵しているのかと思いましたが、温度計になるダイオードが入っているのですね

> 差がほとんどない条件

外気に触れる状態ということですかね

> 測定される電圧は、物性で決まっているので補正をかければ結構精確

この記事の実験だと起動後5分で変化しなくなったので、マイコンの設置状態(外装や通気)に応じた外気温で補正すれば外気温計として使えるということでしょうか
マイコンごとに個別の補正が必要になるのか、マイコンが保有している補正情報を使えば補正は一律で良いのかが気になるところです