測定データと近所の気象庁データとの比較をしてみた。
おおよそ600Pa程度ズレがあるが、傾向は同じである。ちなみに600Paずらしてみてもピッタリ重ならない。まあ、しょうがない。
値のズレが大きい(標高差でも2hPa程度のズレ)なので、調べると部屋を占めると24時間換気の影響で屋内陰圧になることがわかった。半ばわかっていたけど。
別の日の測定結果を見るとこのような形で気象庁の測定データに近い値担っている。せいぜい0.5hPa程度の差に収まっている。
測定データと近所の気象庁データとの比較をしてみた。
おおよそ600Pa程度ズレがあるが、傾向は同じである。ちなみに600Paずらしてみてもピッタリ重ならない。まあ、しょうがない。
値のズレが大きい(標高差でも2hPa程度のズレ)なので、調べると部屋を占めると24時間換気の影響で屋内陰圧になることがわかった。半ばわかっていたけど。
別の日の測定結果を見るとこのような形で気象庁の測定データに近い値担っている。せいぜい0.5hPa程度の差に収まっている。
マイコンでアナログデジタル変換(ADC)を行うとき、ローパスフィルタを入れたち時がある。でも部品が増えるの微妙ってときもある。
また、デジタルフィルタだと、特定の区間だけサンプリングしてフィルタするなどが容易である。
nをフィルタに使う整数、xを入力データとして、x1, x2, x3・・・・とあるものとします。y1=x1として、あとは上記の式に従って計算するとi番目の出力データyiが計算できます。
なお、整数演算する場合などは丸め誤差に注意が必要です。浮動小数点を問題なく扱えるマイコンなら、この限りではありません。
実際にこのフィルタを使った事例について。
関数は初期値30、途中から80になるというステップ関数に、±5の範囲でノイズを与えています(ただしエクセルのrand()関数なので分布が正規分布ではありません)。
関数だと、y=30+50*stepFunction(x) + (rand()-0.5)*10 といった感じ。
n=1は要するにフィルタなしです。上記の式にn=1を入れるとyi=xiとなります。
n=2~n=64まで比較するとアナログフィルタのように、応答が遅れ、終端部が平滑になることがわかります。
最後の100個分のデータを見ると次のよう担っています。
100データの標準偏差をとってみるとわかりますがnが大きいほどばらつきが少なくなります。
8bitマイコンではADCは10bitか8bitくらいなので、16bit変数を用意しておき、
z[i] = (n -1)y[i-1] + x[i] = ny[i-1] - y[i-1] + x[i] = z[i-1] - y[i-1] + z[i]
y[i] = z[i]/n
と、中間にz[i]というn倍大きな値の変数を入れておくと演算の負荷が多少すくなるなる(浮動小数点演算よりは計算がかんたん)。
たとえば2, 4, 8, 16, 32など2^nのサンプリングの場合はビットシフトで掛け算が済むので、すこし早く処理ができます。
日陰というかデスクしたの暗い場所でMPL3115A2を約36時間運用してみた。光の影響はなさそうだ。
気圧変化を気象台の観測データと、と思ったが、残念ながら標高に換算していたため、計算が面倒なのでやめた。台風の時期はこうした記録がおもしろそうだと思う。
リアルタイムクロックとSDカードと組み合わせてマイコンのみでSDカードにデータを記録できればなおよい。
ふと一日の気圧変化を記録に残そうと思ったときに、Arduinoには正確な時間計測の方法がなさそうだとなり、Processingで測定データを受信し、時刻を付与してファイルに記録、といようなことをしてみた。
Arduino側プログラム
MPL3115Aに設定して値を読み込む。今回は標高に換算したデータを出力するようにしています。
タイマー割り込みを使って0.25秒に一回、データをシリアル通信で吐き出しています。もっと遅くてもよかったと今では思ってる。
#include#include #define OverSample 128.0 #include "SparkFunMPL3115A2.h" //Create an instance of the object MPL3115A2 myPressure; LiquidCrystal lcd(12, 11, 5, 4, 3, 2); float Altitude = 0; float Ondo = 0; float Pressure = 0; float tempAltitude = 0; ISR(TIMER1_COMPA_vect) { digitalWrite(13, !digitalRead(13)); Serial.begin(9600); // Start serial for output Serial.print(Ondo,3); Serial.print(" "); Serial.println(Altitude,3); Serial.end(); } void setup() { pinMode(13, OUTPUT); TCCR1A = 0; TCCR1B = 0; TCCR1B |= (1 << WGM12) | (1 << CS12); //CTCmode //prescaler to 256 OCR1A = 156250-1; TIMSK1 |= (1 << OCIE1A); lcd.clear(); lcd.begin(16,2); lcd.setCursor(0,0); lcd.print("Hello! Wait!"); lcd.setCursor(0,1); lcd.print("OverSample:"); lcd.print(OverSample); Wire.begin(); // Join i2c bus // delay(100); // Configure the sensor myPressure.setModeAltimeter(); // Measure altitude above sea level in meters //myPressure.setModeBarometer(); // Measure pressure in Pascals from 20 to 110 kPa myPressure.setOversampleRate(7); // Set Oversample to the recommended 128 myPressure.enableEventFlags(); // Enable all three pressure and temp event flags } void loop(){ myPressure.begin(); // Get sensor online for(int i = 0; i< 10; i++){ Altitude = myPressure.readAltitude(); // Pressure = myPressure.readPressure(); Ondo = myPressure.readTemp(); } while(1){ Altitude = Altitude * (OverSample-1)/OverSample + myPressure.readAltitude()/OverSample; // Pressure = Pressure * (OverSample-1)/OverSample + myPressure.readPressure()/OverSample; Ondo = Ondo* (OverSample-1)/OverSample + myPressure.readTemp()/OverSample; lcd.clear(); lcd.setCursor(0,0); lcd.print(Ondo, 2); lcd.print("C"); lcd.setCursor(7,0); lcd.print(Altitude, 2); lcd.print("m"); } }
Processing側プログラム
import processing.serial.*; PrintWriter file; Serial myPort; // set Serial port to String inString; // string to read from serial port int lf = 10; // ASCII \n void setup() { file = createWriter("balomatic_tester"+nf(year(),4)+nf(month(),2)+nf(day(),2)+"_"+nf(hour(),2)+nf(minute(),2)+".log"); size(200,200); printArray(Serial.list()); myPort = new Serial(this,Serial.list()[1], 9600); myPort.bufferUntil(lf); } void draw() { } void serialEvent(Serial p) { inString = p.readString(); String now; now = nf(year(),4)+"/"+nf(month(),2)+"/"+nf(day(),2)+" "+nf(hour(),2)+":"+nf(minute(),2)+":"+nf(second(),2)+" "; print(now); print(inString); file.print(now + inString); } void keyPressed(){ println(key); if(key == 'f'){ file.flush(); } else if(key == 'q'){ file.flush(); file.close(); exit(); } }
void keyPressed()でやっているのは、fキーを押したときにはファイルをフラッシュ(ディスクに書き出す)を実施、qをクリックしたら実行中止、というようなことになっています。
これでProcessing側でデータを受信して、ファイルに吐き出していく。コンソールには次のようにログが出てきます。
さて、随分前に使ったきり、そのままだった気圧計を再度使ってみる。
シリアルでデータを出力するが、受け取る側が適当だったので、Processingなるソフトでロガーを作って記録してみた。そして気がついたことがある。
MPL3115A2はフリップ実装すると光の影響を受ける。
縦軸は計算した標高、横軸は時間(h)である。
上記はある日一日の0時から日付が変わるちょっと前までのデータ。最後のほうにディップが見られるが、これはデスクランプの白熱灯を点灯した際の影響である。
60m位を指していた値が電気をつけてしばらくしたら、40mを下回っていて、そんな馬鹿なと思って思い立ったのが光の影響。当該のチップは表裏反転して実装しているため、おそらくシリコンウエハがむき出しに近い状態になっており、光の影響を受けていると気がついた。
朝6時少し前に日の出がきっとあったのだろう。10:30くらいには日が陰ったのだろうか。などなどを考えるとそれらしいグラフである。暗いときは安定だが、明るくなると怪しい。
センサーは暗い場所に設置するに限る。
5年前の記事で書いた水晶発振回路を実装した。
5年も放置したのでクリスタルのパッケージが汚れたが、ちゃんと16MHzで動いてくれた。ブレッドボードに差すため、ピンは部品とは反対側に配置してあります。
配線はこんな感じ。
出力はエミッタそのままなので、電圧の1/2を中心とした波形が出力。チップ抵抗などを使って小さくとも思ったが、ある程度大きなほうがいいだろうと思って手持ちのリード線部品で組みました。抵抗やコンデンサの値は過去の記事の通り。
2SK4017をモータードライバに使っていたのだが使っていて挙動がおかしいと思ったフシがあってちょっと調べてみた。
変な挙動というのはMOSFETがONになっているはずなのにVDSが0Vにならないとか、MOSFETをONにしている期間でOFFになってしまっていたりということが見られた。これはゲート電圧がギリギリアウトかなと思って調べたら、アウトだったと思う。
結局の所Vgが2.5Vかそれよりも高いくらいなのだが、電源が3.3V、下の図だと3Vしかなく、十分にMOSFETをONにできていなかったのではという疑問。
データシートを見ても3Vじゃ1Aくらいしか流せないようだ。
さて、モーター回転中の電圧から回転数をセンシングしようというものだが、まずはモーターを別のモーターで回転させながら発生電圧を調べる。
一定電圧ではないので、山あり谷ありの波形だが、最大電圧はこのような感じになっている。6600rpmくらいで2Vくらいの起電力が発生するようだ。
マブチのRE-140RAは3スロット2ブラシなので、1回転6パルスを前提として回転数を算出しています。
電圧波形について、次のPWM回路で調べると、V1とGNDの間の電圧は
このような感じです。波形がギザギザするのはモーターの整流子の都合かと思います。上記の回転数の電圧変化はボトムの電圧を見ています。
マイコン側のプログラムとしては
MOSFETがONのときはADC読まない(読んでもそのまま捨てる)。MOSFETがOFFのタイミングでADC実施をいう方法が良さそう。
図にすると、下のようにPWMのカウンタ(TCNT0)が0-255までカウントする間に、PWM用のOCCR0Bレジスタと、OCCR0Aレジスタの値を少し変更しておき、
OCCAR0AとTCNT0が一致したらADC開始、TCNT0のリセットでADC終了というようなものがいいかと思っている。
OCCR0AはADCを開始したら250などに設定し、TCNT0のオーバーフローを待たずにADC変換やめてもいいと思う。
電圧は上記のようにフラフラ不安定なので上記の回路のようにLPFを使うか、デジタルでフィルタを入れるかして最小値を読むような方法がいいかなと思っている。