温度センサーLM35DZを移動平均で処理する2017年09月26日 11時12分50秒


ADCからのセンサーデータをそのまま使うと様々な要因により値がふらつきます。
それを平滑化させるために移動平均という手法があります。

■移動平均の考え方
同じセンサーデータをある程度の数だけ格納してその数の平均値を求めるのですが、次々に新しい観測データを採用して古いデータを捨てていくという処理が移動平均です。

■移動平均に必要な処理構造
(1) センサーデータを一定数配列に保管します
(2) 一定数のセンサーデータの合計値を求めます
(3) センサーデータの合計値を格納数で割ると平均が求められます
(4) 配列に新しいデータを格納する時には一番古いデータを捨てます
(5) 一番古いデータを捨てる前にそのデータを合計値から引きます
(6) 合計値に新しいデータを加えます
(7) センサーデータ単体で平均値を求める代わりに最終的な換算時に加味することもできます

■サンプルプログラム
以前の記事のプログラムをベースに修正しています。
以下の部分が変わっています。
(1) 換算用のマクロの変更
(2) 配列へのADCデータ格納と合計処理の追加
(3) 配列インデックスをLED点滅に流用
(4) シリアル出力に合計値出力を追加
(5) 温度換算時に使うデータはADC出力の合計値を使う
移動平均版LM35DZ処理スケッチ

出力結果は以下のようになります。

移動平均版でのLM35DZ処理出力

■注意点
今回のサンプルではサンプリング周期と表示周期を同じ1秒としていますので室温測定には問題ありませんが、もっと早い応答性が必要な場合にはサンプリング周期をもっと短い間隔にして、表示や出力間隔とは分ける必要があります。

ADCの出力値をアナログ値に換算する考え方2017年09月25日 15時11分05秒

■ADC出力値からアナログ値を求めるやり方の問題
ADCから量子化されて出力されるデジタル値をアナログ値への換算を行う時に2^nで割るのか、2^n - 1で割るのかの問題ですが、ADCの内部の仕組みとしては以下図の左側で示したような配列とその要素のインデックスだと考えると問題が整理しやすくなります。
2^nとか2^n-1の代わりにここでは10bitADCを前提として1024と1023という数値でどちらかを表すことにします。

ADC出力値の換算方式の比較図


この図の左側で示した配列の条件は以下です。
(1) メモリ上の通常の配置とは逆のイメージ
(2) 10bitADC
(3) 基準電圧(VREF)は5.0V
(4) 1LSB = 5.0/1024.0 = 0.004882813V
(5) 上限(n) = 下限(n+1)未満の値

■1024で割る派の問題
ADCから出力されたデジタル値からアナログ値に換算するやり方で、1024で割る派の人たちは上記の「仕組み」を前提に話をしているわけですが、常に配列インデックスに対応したアナログ値の範囲の下限のデータを拾ってくることになります。
理屈から言えば上限を拾ってきても良いはずだし、下限と上限の中間の値を採用しても良いはずです。
この点が1024で割る派の人たちのおかしなところです。
現実的には下限であることは非常にまれでしょう。
シフト演算して処理を高速化するために1024で割るという主張の方がよっぽど説得力があります。

■1023で割る派には問題はありません
あえて問題があるとすればシフト演算での高速化はできません。
あくまでもブラックボックスとしてのADCを扱うため、出力値が0なら0Vだし、1023なら5Vとして係数を求めて度量換算する算数の問題として計算するだけです。
結果的に中間値を採用することになり、フルスケールに達すると確実に5Vになります。
もちろん、中間値とはいっても0の時は下限値を採用し、1023の時は上限値を採用するような変則的な対応付けが自動で行われることになります。
図の右側で示した通り左側の配列の下限値から上限値の間に換算値は収まっていることが分かります。
無思想でコピペ増殖で1024で割る派が多い中、数少ない1023で割る派の書籍はこちら

とは言え、実験してみるのは筋なので、実際のセンサーでの実験結果はこちら

温度センサーLM35DZを実用域で使う2017年09月25日 01時17分37秒

秋月通商などで手に入る安価な温度センサーにLM35DZというものがあります。
これをArduinoと組み合わせて使う記事は検索するといくつも出てくるのですが、ほとんど「コピペ」で有用な追加情報はほとんど得られない状況です。

そこで、いくつか実験も兼ねてテストしてみようと思います。
以下は実験風景です。
実験風景

多くの記事は基準となる温度計などとの測定値の比較をして検証を行うなどの基本的なことが行われていません。
また、データシートの読み込みが足りなかったりADCからの取得値のデータ処理の仕方の根拠説明がありません。

今回の実験では比較のために他の温度センサーでの観測値と市販のデジタル温度計の測定値を使います。
「他の温度センサー」としてはセンサーそのものがデジタル値を返すセンシリオンのSHT-11を使いました。これはADCによる変換処理の仕方の影響を排除するためです。

■動作電圧
LM35DZに印加する電圧範囲は広く、4V~20Vとなっています。
実験風景でオレンジ色はArduinoの5Vに接続しており、赤色は3.3Vに接続しているのですが、実は実験の結果、3.3Vで十分に動作することが分かったため、3.3Vで動作させることにしました。



■ADC基準電圧
ArduinoのデフォルトのADC基準電圧は5Vです。
一方でLM35DZを追加部品なしに使う場合は氷点下の測定はできず、10mV/℃という特性なので100℃の場合は1000mV、つまり1Vの電圧程度しか測る必要はないでしょう。このようなセンサーをADCで使う場合は基準電圧を下げるかアンプなどで増幅するなどしてフルスケールと基準電圧が近くなるようにします。
Arduinoはデフォルトの設定以外にもINTERNALという指定をすることにより機種ごとの異なる電圧をADCの基準電圧として使うことができます。
Arduino UNOの場合はその電圧は1.1Vです。
今回は1.1Vを基準電圧に設定します。
なお、通常の5Vが基準電圧の場合、生活環境での室温は明らかに他のセンサーの測定値よりも小さい値となりましたので、実用にならないことを確認しています。
スケッチは以下になります。
デフォルトと1.1Vの設定処理はマクロで切り替えるようにしていますのでどれ位値が変わるかは試してみると分かります。
ArduinoIDE上のスケッチ

■ADC基準電圧とプログラムの対応付け
5Vの基準電圧とか3.3Vの基準電圧とか1.1Vの基準電圧とかいろいろありますが、本当にそれらの電圧が基準電圧として正しいのでしょうか?
5Vのつもりが5.01Vであったり、4.98Vであったりします。
当然プログラム上はそれに合わせて実測値を採用しないと変換処理の誤差となります。
今回はそこまで実行していませんし、1.1Vの基準電圧の場合は外部から正確に測ることもできませんから、理論値として処理するしかありません。
また、これらの基準電圧そのものが温度によって変動する場合もありますので、センサーを使った測定というのはそれだけ奥が深いものだということを忘れないでいただきたいと思います。

■10bitADCでの換算について
10bitADCを使った変換処理で最近よく見かけるのは1024で割っている処理です。
ソフトウェアとしての前提条件とブラックボックスとしてのADCを扱う場合はこれは算数的には間違いです。
0Vの時に0が出力され、5V(1.1V)の時に1023が返るのですから、1024という数字はどこから出てくるのでしょうか?
例えば「10kmの距離を5時間かけて歩きました。時速は?」という問題を解くときに10km/6なんて計算しますか?しませんよね。
0~1023は1024段階だから1024?違います。
上記の時速の問題で0~5の6段階だから6で割る、ではないでしょう?
1024にする場合の理由はもちろんあるのですが、算数の問題の前提条件を無視するのは違います。
理屈は別の記事に譲ることにして、上記のスケッチでは両方試しています。

■他の温度センサーと温度計との比較
全部バラバラでした。
あえて近い組み合わせを選ぶとセンシリオンのセンサー出力値とADC基準電圧1.1Vで1023で割り算した値でした。とはいえ、1024で割ったものもそれ程差があるわけではありません。
単体の温度計の値は冒頭の写真を参考にしてください。HAKUBAの温度計は近い値を示しています。
センシリオンセンサーなどを使った他のシステムの出力
上記のデータはカンマで区切ってありますが、左から3番目の値がセンシリオンのSHT-11というセンサーを使った温度測定値です。
今回のスケッチでのシリアル出力結果
上記が今回試したスケッチでの実行結果です。

■Lチカの理由
上記のスケッチではLEDを点滅させていますが、これは他の負荷がある場合にADCへの影響があるかどうかを確かめるためです。
試しにA0に3.3Vを直結してAREFをデフォルトの5Vにしてみると分かります。
固定値のはずがふらついています。
もちろんこれはLチカの影響だけではありません。要はADCはセンサーと接続しない場合でも値がブレるということの確認です。

基板設計はケース加工を考えてやろう!2017年07月22日 22時59分13秒

久しぶりにEAGLEと格闘していました。
自作のモータードライブコントローラ基板をケースに組み込む時にこれまでは手動現物合わせで加工していたのですが、面倒になったのでタカチの加工サービスを利用することを具体的に進めることにしました。
その為には基板の穴の位置とか部品の位置を測る必要があります。
出来上がった基板をノギスで測るのもバカバカしいので基板設計に使ったEAGLEで寸法線をいれてみた、というわけです。

いやあ、部品配置するときのグリッド設定を細かくし過ぎた弊害ですね。
今後はせめて0.5mmグリッドで部品配置するようにします。
反省反省。


部品配置や穴あけ寸法が半端

Sensirion製温湿度センサーの湿度が100%になる?2016年05月09日 07時34分56秒

自作のWeatherSensorShield

現象

Weatherduinoという製品のサンプルライブラリをベースにセンシリオン製のSHT-11という温湿度センサーからのデータ取得を行っておりました。

ソースコードを何度確認してもデータシート通りの演算のように見えるのに、観測結果の湿度がいとも簡単に頻繁に100%に達してしまうのです。

Weatherduino用のライブラリはこちらから入手可能です。

このセンサーの入手はストロベリーリナックスが比較的安くて良いでしょう。

Weatherduinoのサンプルプログラムのバグ

さて、本題です。 上記の菅工房のサンプルライブラリG7Weather3.cppのSHT-11の処理では以下の演算部分があります。

// SHT11 温度(℃)・湿度(%)取得
void G7Weather::get_sht11 (float *temp, float *humi) {
       unsigned short sot, sorh;
       float t, rhl, rht;
 
       // 温度読込み
       sht_tsseq();
       sht_write(0x03);
       SHT_SDA_H;
       while (SHT_SDA); // 変換終了待機
       sot = (unsigned short)sht_read(1) << 8;
       sot |= sht_read(1);
       sht_read(0);

       // 湿度読込み
       sht_tsseq();
       sht_write(0x05);
       SHT_SDA_H;
       while (SHT_SDA); // 変換終了待機
       sorh = (unsigned short)sht_read(1) << 8;
       sorh |= sht_read(1);
       sht_read(0);

       // 温度補正 14bit (3.3V)
       sot &= 0x3fff;
       t = -39.66 + 0.01 * (float)sot;
       *temp = t;

       // 湿度補正 12bit
       sorh &= 0x0fff;
       // rhl = -4.0 + 0.0405 * sorh + (-2.8 * 0.000001) * (sorh * sorh); // Ver.3
       rhl = -2.0468 + 0.0367 * (float)sorh + (-1.5955 / 1000000.0) * (float)(sorh * sorh); // Ver.4
       rht = (t - 25.0) * (0.01 + 0.00008 * (float)sorh) + rhl;
       if (rht > 99.0) rht = 100.0;
       *humi = rht;
}

この処理の中でのバグは以下です。

(float)(sorh * sorh)

この部分だけですね。問題があるのは。 sorhという変数はunsigned shortで有効なデータは12ビット分です。単独で使う場合は16ビットで十分ですが2乗すると演算結果は16ビットでは足りなくなります。

バグ修正

なので、以下のように修正の必要があります。

(float)((long)sorh * (long)sorh)

観測データは12ビットなので最大値は4095となります。 それを2乗すると16769025となり16ビットの最大値である65535を 超えてしまっていたわけです。

一方、符号付のlongの正の最大値は2147483647ですので十分に格納可能になります。 この修正により、湿度が簡単に100%になる問題は解消しました。 原因解明に3年かかりました。

ちなみに、写真の基板はWeatherduinoではなく自作品です。 ArduinoUnoとの組み合わせで使っています。