はじめに
前回はArduino NANOを使用して温度センサLM60BIZからのデータ取得を行ってみました。
温度データを取得することはできましたが、測定値が違うという結果になってしまいました。
今回は、なぜ温度誤差が生じたのか?対策案についてまとめます。
前回の内容はこちらから
温度がずれる原因と対策
調べて出てきた原因を書き出してみます。
電源電圧の影響(Vcc補正)
ArduinoのADC(analogRead()
)は、Vccを基準にして電圧を測定します。
例えば、LM60BIZの出力が正しくても、基準となるVccが 5.0Vでなく4.7V などだった場合、
「5Vを基準にした計算」では値がずれてしまいます。
対策として、readVcc()
関数で実際のVccを測定して補正することが挙げられます。
センサのオフセット誤差
LM60BIZの仕様上、0℃時の出力は 424mV ± 6mV(最大±1℃相当) の誤差があります。
実際の室温(25℃)でも ±1〜2℃の誤差が出ることがあります。
対策として、- 424
を - 420
に調整するなどのオフセットの微調整を行うことが考えられます。
ADCノイズ・分解能
Arduinoの10ビットADCは、1ステップ ≒ 4.88mV(大体5V)。
これは約 0.78℃ 相当の分解能であり、小さな温度変化や誤差が出やすいです。
また、ノイズの多い電源や配線が長い場合、値が不安定になることも…
そこで、analogRead()
を複数回読んで平均化することで対処ができるかもしれません。
また、センサ近くに0.1μFのデカップリングコンデンサを入れることも1つの手段です。
センサの設置位置の問題
LM60BIZがArduinoや他の部品からの放熱に影響されていると、実際の空気の温度と異なる値を示すことがあります。センサを周囲の空気中に浮かせるように配置することや、放熱源(電圧レギュレータやIC)から離すことも大切です。(今回はブレッドボードなので問題ない?)
修正する
いろいろな方法がありますが、まずは手っ取り早くできる電源電圧の影響(Vcc補正)をしてみます。
Arduinoでは内部1.1V基準を使ってVcc(基準電圧)を測定し、その値を使ってADC計算の補正を行うことができます。これによって、電源電圧のばらつきによる誤差を軽減できます。
方法として、
1. 内部1.1VリファレンスをADCで測定
→ 実際のVccを計測する。
2. そのVccを使ってLM60BIZの出力電圧(mV)を算出
3. 出力電圧から温度を算出
この流れで再度プログラムを修正します。
修正版コード(基準電圧計測Ver.)
const int sensorPin = A0;
// 内部1.1Vリファレンスを使ってVcc(電源電圧)を計測
long readVcc() {
// ADMUX構成:内部1.1Vを測る(MUX=0b1110)
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // 安定待ち
ADCSRA |= _BV(ADSC); // 測定開始
while (bit_is_set(ADCSRA, ADSC)); // 終了待ち
uint16_t result = ADCL;
result |= ADCH << 8;
long vcc = 1125300L / result; // 単位: mV(1.1V基準 × 1023 × 1000)
return vcc;
}
void setup() {
Serial.begin(9600);
}
void loop() {
long vcc = readVcc(); // 電源電圧(mV単位)
int adcValue = analogRead(sensorPin); // センサのADC値取得
float voltage_mV = (adcValue / 1023.0) * vcc; // 実電圧に変換(mV)
// LM60BIZの温度計算式
float temperatureC = (voltage_mV - 424.0) / 6.25;
// 出力
Serial.print("Vcc = ");
Serial.print(vcc);
Serial.print(" mV, 温度 = ");
Serial.print(temperatureC);
Serial.println(" °C");
delay(1000);
}
上記の内容にて確認しました。
前回と比較すると、近似値になったように感じます。
修正版コード(平均化Ver.)
その後もいろいろと試したり調べたりして、最終的に移動平均を取るようにしました。
メリットとしてはノイズ低減ができること。計測誤差を滑らかな温度となるように補正してくれることなどがあります。
移動平均のメリット
特徴 | 効果 |
ノイズ低減 | 1回ごとの計測では±数℃ずれることがある。 (電源ノイズや周囲の電磁波) これらの揺らぎを吸収できる |
滑らかな変化 | 室温が徐々に変化している 移動平均にすると、なだらかな変化になる |
フィルタとしての効果 | 急な変化を除去できる |
グラフ表示時に有利 | 温度ログを可視化する際に滑らかなグラフになる →視認性が向上 |
移動平均のデメリット
特徴 | 効果 |
反応が遅くなる | 温度の急な変化に対する反応が鈍くなる →反映にラグが生じるすぐには表示されません。 |
記録用メモリを使う | 過去のデータを複数回分保持しておく必要がある →コードが複雑になります。 |
コードも書いておきます。
const int sensorPin = A0;
const int bufferSize = 10;
float temperatureBuffer[bufferSize];
int bufferIndex = 0;
long readVcc() {
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2);
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC));
uint16_t result = ADCL;
result |= ADCH << 8;
long vcc = 1125300L / result; // 単位: mV
return vcc;
}
void setup() {
Serial.begin(9600);
delay(1000); // センサ安定待ち
// 現在の温度でバッファを初期化
long vcc = readVcc();
int adcValue = analogRead(sensorPin);
float voltage_mV = (adcValue / 1023.0) * vcc;
float initialTemp = (voltage_mV - 424.0) / 6.25;
for (int i = 0; i < bufferSize; i++) {
temperatureBuffer[i] = initialTemp;
}
}
void loop() {
// 現在の温度を取得
long vcc = readVcc();
int adcValue = analogRead(sensorPin);
float voltage_mV = (adcValue / 1023.0) * vcc;
float temperatureC = (voltage_mV - 424.0) / 6.25;
// 温度バッファに格納
temperatureBuffer[bufferIndex] = temperatureC;
bufferIndex = (bufferIndex + 1) % bufferSize;
// 移動平均の計算
float sum = 0.0;
for (int i = 0; i < bufferSize; i++) {
sum += temperatureBuffer[i];
}
float averageTemp = sum / bufferSize;
// 結果を出力
Serial.print("移動平均温度(10秒間): ");
Serial.print(averageTemp, 2);
Serial.println(" °C");
delay(1000); // 1秒ごとに更新
}
コードの説明
自分も分からなくなるとまずいので、一応説明も。
定数と変数の定義
const int sensorPin = A0;
const int bufferSize = 10;
float temperatureBuffer[bufferSize];
int bufferIndex = 0;
sensorPin
: LM60BIZが接続されているアナログピン(A0)bufferSize
: 移動平均の窓幅(10秒分)temperatureBuffer[]
: 過去の温度を保持する配列bufferIndex
: 次に書き込む配列のインデックス
readVcc()関数(内部電圧リファレンスを用いてVccを測定)
long readVcc() {
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2);
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA, ADSC));
uint16_t result = ADCL;
result |= ADCH << 8;
long vcc = 1125300L / result; // 単位: mV
return vcc;
}
Arduinoの内部1.1Vリファレンス電圧をADCで測定
→現在のVccを逆算する。1125300L / result
は、1.1V × 1023 × 1000(定数)を使ってVccをmVで返します。
setup()関数(初期化)
void setup() {
Serial.begin(9600);
delay(1000); // センサ安定待ち
}
ここはいつも通りの設定。
シリアル通信の設定を開始とセンサが安定するまで少し待機している。
loop()関数(メイン処理)
long vcc = readVcc();
int adcValue = analogRead(sensorPin);
float voltage_mV = (adcValue / 1023.0) * vcc;
float temperatureC = (voltage_mV - 424.0) / 6.25;
analogRead()
でADC値を取得(0~1023)- 測定したVccを基に、**実際のセンサ出力電圧(mV)**に変換
- LM60BIZの仕様に基づき、温度に換算する
\(温度[℃]=\frac{電圧[mV] – 424}{6.25}\)
- 移動平均バッファに新しい温度を追加
temperatureBuffer[bufferIndex] = temperatureC;
bufferIndex = (bufferIndex + 1) % bufferSize;
- 配列の該当位置に新しい温度を上書き
bufferIndex
を 0~9 の間で循環させる(リングバッファ)- 移動平均を計算して表示
float sum = 0.0;
for (int i = 0; i < bufferSize; i++) {
sum += temperatureBuffer[i];
}
float averageTemp = sum / bufferSize;
Serial.print("移動平均温度(10秒間): ");
Serial.print(averageTemp, 2);
Serial.println(" °C");
temperatureBuffer
の全要素を合計し、平均を出力- 結果をシリアルモニタに表示(小数点第2位まで)
まとめ
ということで、今回はここまで。
当初と比べると、結構近い温度を取得できるようになったと思います。
次はこの取得したデータをどのように扱うかかな
ずっとシリアルで取得して見るわけにもいかないし…
参考リンク、使ったもの
- TI公式 LM60BIZ データシート
- Arduino Nano互換ボード(ATmega328P)(Type-B/Type-C)
電子工作ステーションにて購入 \890
- 高精度IC温度センサー LM60BIZ
秋月電子:販売コード 102490
コメント