google-site-verification: google3bd66dd162ef54c7.html
fc2ブログ

アナログ気圧計2号機、ソフト解説

 時計のメカを利用したArduinoで動かすアナログ表示の気圧計を作る話の続きです。前回の記事ではハードについて解説しましたが、今回はソフト解説です。

▼アナログ気圧計
アナログ気圧計

◆主な仕様、特徴
1. 海面気圧を表示
 気圧にはその場所の絶対気圧と標高0mに換算した海面気圧の二種類があります。どちらを表示してもいいのですが、この気圧計では海面気圧を表示させます。天気予報などで出てくる気圧は海面気圧なので、この方が判り易いだろう、というのが一番の理由です。ちなみに、普通のインテリアのメカニカルな気圧計も海面気圧を表示するように調整されていると思います。

2. 海面気圧への換算
 気圧センサーから読み出される値は絶対気圧なので、これを海面気圧に換算する必要があります。これを正確に行う換算式があるのですが、計算が面倒だし、標高と気温などの情報も必要になります。ということで、手抜きをして、単に一定のオフセットを加えるだけで済ますことにしました。標高があまり高くなければこの方法でも大した誤差は発生しないと思います。

 ところで、ソフトの中でオフセットを加えるなどという操作をしなくても、単に指針の位置をずらしてセットしておくだけでも同じことになります。実際に気圧計の1号機ではこうやっていました。ただこの方法には一つ大きな問題があります。それは、バッテリーアラームになった時は針を特定の位置に動かす必要があるのですが、何しろソフトは針がどこにあるか把握していないので正確な位置に針を移動することが出来ません。(大雑把にやるなら、過去の最大気圧+αの位置に針を動かすなどの手があります)

 ということで、海面気圧へ換算するためのオフセット値はソフト的に設定することにしました。あと、どうせ気圧センサーの誤差もあるでしょうから、それもこのオフセット値で補正することにします。

3. 電池アラーム機能
 電池電圧を監視しておいて、3.4V以下になったらバッテリーが空になったと判断し、指針を Low Battery エリアに移動。更にLEDを点滅させて知らせます。

4. 省エネ機能
 待機時はCPUをパワーダウンモードに入れて省電力化を図っていますが、そのためにdelayWDT関数を使っています。また気圧センサーもパワーダウンモードに入れて省電力を図っています。なお、気圧の変化はとてもゆっくりしているので、測定は10分間隔で行っています。
 また、無駄な針の動きを防止するために不感帯を設けてあり、5パルス(0.1hPa)以内の気圧変化はでは針を動かさないようにしています。これにより針の動きには 0.1hPaのヒステリシスが入りますが、たぶん気にならない程度だと思います。

5.デバック支援機能
 起動時のジャンパーの状態によって、2秒間隔動作モードと時計の針の運針デモ(±10パルスの範囲を連続動作)を行います。なお、2秒間隔モードでは動きが判り易いように指針の不感帯は無しで動きます。
 何もジャンパーを設定しないと通常モードで動きます。

▼スケッチ
 少し長いですが、スケッチは以下の通りです。なおボードは Arduino Pro/Pro mini (3.3V 8MHz) としてコンパイルします。あと、リストの後に少しばかりポイントを解説しておきます。

/*
 気圧センサーLPS25Hを使ったアナログ気圧計

・アナログ時計のムーブメントで表示。スケールは、1hPa=50秒。
・電池電圧低下で自動停止。指針は表示範囲外(1043hPa付近)に移動。
・単三電池3本で3年以上の連続動作(予定)
 2017/4/27 ラジオペンチ http://radiopench.blog96.fc2.com/
*/

#include <Wire.h> // I2Cドライバ
#include <avr/sleep.h> // delayWDTで使用
#include <avr/wdt.h> // dalayWDTで使用

#define divR 5.795 // 電池電圧測定の分圧比(divider Ratio デフォルト=5.7)
#define pOffset 11.8 // 海面気圧への補正値(hPa)、設置場所で要調整

#define voltageLimit 3.4 // バッテリー電圧低下検出閾値 3.4V
#define lowBatPosi 1043.0 // バッテリーアラーム表示位置 1043.0hPa

// 時計のパルスモーター駆動波形パラメーター
unsigned long cwPulse = 31000UL; // 順回転パルス幅(us) 標準は31000
unsigned long cwInterval = 25000UL; // 順回転パルス間隔(us) 限界は 25000

unsigned long ccwPulse1 = 5500UL; // 逆回転パルス幅(us) 5500
unsigned long ccwWait = 2000UL; // 反転待ち時間(us) 2000
unsigned long ccwPulse2 = 30000UL; // 逆回転パルス幅(us) 標準は30000
unsigned long ccwInterval = 60000UL; // 逆回転パルス間隔(us) 限界は60000

int sleep_n = 75; // スリープ回数
int sleep_t = 9; // スリープ時間指定 9=8秒 (75*8=600秒=10分)

int dedZone = 5; // 時計ドライブの不感帯(このパルス以上なら動かす)

boolean flag = true; // パルスモーター駆動極性フラグ

float hPa, volt; // 気圧、電圧変数
long p24; // 24ビット形式の気圧測定結果デ-タ

int LPS25H_adr = 0x5D; // I2C address. SDO=HIGHで0x5D (0x5Cでは消費電流が5μA増える)

unsigned int clkPosi, clkPosiLast; // 針の位置 (1hPa=50pulse, max=65k=1310hPa)

void setup() {
pinMode(7, INPUT_PULLUP); // 動作モード指定スイッチ
pinMode(8, INPUT_PULLUP); // 動作モード指定スイッチ

pinMode(9, OUTPUT); // conect coil thrugh 220Ω
pinMode(10, OUTPUT); // coil return
pinMode(13, OUTPUT); // 動作表示LED

analogReference(INTERNAL); // ADCを内部refで使う FS=1.1V
Wire.begin();
Serial.begin(115200);
Serial.println(); // 空送り
if (digitalRead(7) == LOW) { // もしPin7がGNDなら
for (;;) { // 無限ループで
clockDemo(); // 時計の抵抗調整プログラム(リセットで脱出)
}
}
if (digitalRead(8) == LOW) { // もしPin8がGNDなら動作確認用に
sleep_n = 1; // ループは1回で
sleep_t = 7; // 周期は2秒
dedZone = 0; // 表示不感帯無しで動かす(解除はリセット)
}

LPS25H_check(); // 気圧センサ(LPS25H)との通信確認
LPS25H_setup(); // LPS25H初期化
LPS25H_measure(); // 初回の気圧測定
clkPosiLast = hPa / 0.02; // 現在の表示位置を記録(針は合っているものとみなす)
Serial.print("Start press = "); Serial.print(hPa); Serial.println("hPa");
Serial.println(); Serial.flush(); // 改行して送信完了待ち
}

void loop() {
LPS25H_measure();
Serial.print(hPa, 2); Serial.print(", ");
clkPosi = hPa / 0.02; // 針の位置を計算
moveTo(clkPosi); // 針を指定位置に動かす
volt = batteryCheck(); // 電池電圧確認(電池が空なら関数内で処置)
Serial.println(volt); // 電池電圧表示
Serial.flush();
// delay(10);
for (int i = 0; i < sleep_n; i++) { // 次回測定まで待機(75回)
delayWDT(sleep_t); // パワーダウンスリープ
}
}

void LPS25H_check() {
if (LPS25H_read(0x0F) != 0xBD) { // I2C上にLPS25Hがあるか確認
Serial.print("LPS25H not found");
for (;;) { // ダメならここで無限ループ
}
}
Serial.println("Barometer start. LPS25H found at I2C 0x5D");
}

void LPS25H_setup() { // センサーの初期設定
LPS25H_write(0x20, 0x80); // 電源ON(PD=1), ブロックデーター更新(BDU=1)
delay(1); // パワーダウンから通常モードに変わるまで待つ
LPS25H_write(0x20, 0x84); // 電源ON(PD=1), ブロックデーター更新(BDU=1)

LPS25H_write(0x10, 0x03); // アベレージ回数設定:温度8回,気圧512回
LPS25H_write(0x21, 0x01); // FIFO無効、ワンショット動作
delay(1);
LPS25H_write(0x20, 0x04); // 電源を一旦OFF(PD=0), ブロックデーター更新(BDU=1)
delay(100);
}

void LPS25H_measure() {
digitalWrite(13, HIGH);
LPS25H_write(0x20, 0x00); // Power down for clean start
delay(1);
LPS25H_write(0x20, 0x84); // シングルショットモードでPower ON
LPS25H_write(0x21, 0x01); // 測定スタート
while (LPS25H_read(0x21) != 0x00) { // 測定が終わるのを待つ
}
p24 = LPS25H_press(); // 気圧読み取り
LPS25H_write(0x20, 0x00); // パワーダウンモードに戻す
hPa = ((float)p24 / 4096.0) + pOffset; // 気圧計算
digitalWrite(13, LOW);
}

long LPS25H_press() { // 気圧データー読み取り処理
long x, x1, x2, x3;
x1 = LPS25H_read(0x28); // 気圧データー読み出し
x2 = LPS25H_read(0x29);
x3 = LPS25H_read(0x2A);
x = (x3 << 16) | (x2 << 8) | x1; // 気圧表示結果合成
return x; // 24ビット形式で気圧の値を返す
}

void LPS25H_write(byte adr, byte data) { // LPS25Hの指定アドレスに書き込み
Wire.beginTransmission(LPS25H_adr);
Wire.write(adr);
Wire.write(data);
Wire.endTransmission();
}

byte LPS25H_read(byte adr) { // LPS25Hの指定アドレスを読み出し
Wire.beginTransmission(LPS25H_adr);
Wire.write(adr);
Wire.endTransmission();
Wire.requestFrom(LPS25H_adr, 1); // 1バイトだけ読む
return Wire.read();
}

float batteryVoltage() { // バッテリー電圧測定
int data;
float voltage;
data = analogRead(0); // ADCを読む
voltage = divR * 1.1 * data / 1024.0; // 電圧に換算
return voltage; // 電圧をfloatで返す
}

void moveTo(unsigned int x) { // 指定した位置に針を動かす
int p;
p = x - clkPosiLast;
Serial.print(x); Serial.print(", "); Serial.print(p); Serial.print(", ");
if (( p < -dedZone) || ( dedZone < p)) { // 移動量が不感帯以上あったら、
clockDrive(p); // 針を動かす
clkPosiLast = x; // 針の位置を保存
}
}

void clockDrive(int x) { // 引数の値だけ針を動かす(引数は正負)
if ( x != 0) {
if ( x > 0) {
cw(x);
}
else {
x *= -1;
ccw(x);
}
}
} // clockDrive

void cw(int n) { // 指定パルスだけ順回転
if (n != 0) {
for (int x = 1; x <= n; x++) {
cwP();
}
}
}

void ccw(int n) { // 指定パルスだけ逆回転
if (n != 0) {
for (int x = 1; x <= n; x++) {
ccwP();
}
}
}

void cwP() { // 順回転パルス発生
digitalWrite(13, HIGH); // LED flash
flag = ! flag;
if (flag == true) {
digitalWrite(9, HIGH); // coil drive foward
timeWait(cwPulse); // パルス幅
digitalWrite(9, LOW); // coil drive end
}
else {
digitalWrite(10, HIGH); // coil drive revers
timeWait(cwPulse); // パルス幅
digitalWrite(10, LOW); // coil drive end
}
digitalWrite(13, LOW); // LED flash end
timeWait(cwInterval); // パルス間隔
}

void ccwP() { // 逆回転パルス発生
digitalWrite(13, HIGH); // LED flash
flag = ! flag;
if (flag == true) {
digitalWrite(9, HIGH); // coil drive foward
timeWait(ccwPulse1); // 先頭パルス幅
digitalWrite(9, LOW); // coil drive end

timeWait(ccwWait); // 反転待ち時間

digitalWrite(10, HIGH); // coil drive revers
timeWait(ccwPulse2); // 反転パルス幅
digitalWrite(10, LOW); // coil drive end
}
else {
digitalWrite(10, HIGH); // coil drive revers
timeWait(ccwPulse1); // 先頭パルス幅
digitalWrite(10, LOW); // coil drive end

timeWait(ccwWait); // 反転待ち時間

digitalWrite(9, HIGH); // coil drive foward
timeWait(ccwPulse2); // 反転パルス幅
digitalWrite(9, LOW); // coil drive end
}
digitalWrite(13, LOW); // LED flash end
timeWait(ccwInterval); // パルス間隔
}

void clockDemo() { // 時計の動作確認プログラム(正負に10パルス動かす)
clockDrive( 10); delay(300);
clockDrive(-10); delay(600);
clockDrive(-10); delay(300);
clockDrive( 10); delay(600);
}

void timeWait(unsigned long xx) { // μsの値だけ待つ
unsigned int x;
if ( xx > 16000) { // 16000以上なら
delay(xx / 1000); // dalay関数を使用(1000us以下の端数は切り捨て)
} else {
x = xx;
delayMicroseconds(x); // 16000以下なら delayMicrosecondsを使用(引数はunsigned int)
}
}

float batteryCheck() { // 電池電圧チェックと電池切れ処理
float v;
v = batteryVoltage();
if (v < voltageLimit) { // バッテリー電圧が基準以下なら
moveTo(lowBatPosi / 0.02); // 針をLowBattery表示ゾーンに移動させて
while (1) { // 無限ループでLED点滅
digitalWrite(13, HIGH);
delayWDT(0); // LED Flash 16ms
digitalWrite(13, LOW);
delayWDT(8); // wait 4sec
}
}
return v; // 電圧OKならバッテリー電圧を返す
}

void delayWDT(unsigned long t) { // パワーダウンモードでdelayを実行
delayWDT_setup(t); // ウォッチドッグタイマー割り込み条件設定
ADCSRA &= ~(1 << ADEN); // ADENビットをクリアしてADCを停止(120μA節約)
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // スリープはパワーダウンモードで行うことを指定
sleep_enable();
sleep_mode(); // この命令実行後スリープ開始
sleep_disable(); // WDTタイムアップでここから動作再開
ADCSRA |= (1 << ADEN); // ADCの電源をON
}

void delayWDT_setup(unsigned int ii) { // ウォッチドッグタイマーをセット。
// 引数はWDTCSRにセットするWDP0-WDP3の値。設定値と動作時間は概略下記
// 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
// 6=1sec, 7=2sec, 8=4sec, 9=8sec
byte bb;
if (ii > 9 ) { // 変な値を排除
ii = 9;
}
bb = ii & 7; // 下位3ビットをbbに
if (ii > 7) { // 7以上(7.8,9)なら
bb |= (1 << 5); // bbの5ビット目(WDP3)を1にする
}
bb |= ( 1 << WDCE );

MCUSR &= ~(1 << WDRF); // MCU Status Reg. Watchdog Reset Flag ->0
// start timed sequence
WDTCSR |= (1 << WDCE) | (1 << WDE); // ウォッチドッグ変更許可(WDCEは4サイクルで自動リセット)
// set new watchdog timeout value
WDTCSR = bb; // 制御レジスタを設定
WDTCSR |= _BV(WDIE);
}

ISR(WDT_vect) { // WDTがタイムアップした時に実行される処理
}
 細かいところはプログラム中のコメントを見て頂ければと思います。重要なポイントは、
・15行目 pOffset 11.8  海面気圧へ補正するためのオフセット値です。センサーの誤差補正も兼ねています。11.8hPa
・17行目 voltageLimit 3.4 電池電圧がこの値(3.4V)以下になったら電池が空になったとみなしてそのむね表示します。
・21~27行目 は時計のパルスモーターの駆動条件(パルス幅)です。ここは別途調整する必要があります。

◆調整方法
 気圧の測定結果(海面気圧)をシリアルモニタに流すようになっているので、その値が近くの気象台から発表されている気圧と合うように pOffset を調整します。この調整は測定周期2秒モードにして行った方がやり易いです。なお、pOffsetの調整には再コンパイルが必要です。
  pOffsetの調整が済めば気圧計の内部で海面気圧が得られるようになっています。そこで指針を気象台から発表されていいる気圧に合わせればその後はずっと海面気圧を表示するようになります。
 一旦pOffsetの調整が済んでいれば、電池交換などで気圧計の電源を入れ直した場合は単に指針を気象台の発表する気圧に合わせるだけで大丈夫です。なお、この気圧計を標高の違う場所に持って行った場合は再度調整を行う必要があります。

◆まとめ
 ということでソフト作成が終わり、この気圧計作りもおしまいです。

 この気圧計が本当に3年間動くか注目していきたいと思います。まずは新品の電池を入れて動かし始めますが、様子は時々このブログで紹介したいと思いますのでお楽しみに。
カレンダー
03 | 2017/04 | 05
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 - - - - - -
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。
記事の内容のご利用は読者の自己責任でお願いします。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード