google-site-verification: google3bd66dd162ef54c7.html

Arduinoで日の出・日の入り時刻を計算

 先日コメントで Arduino で日の出・日の入り時刻の計算が出来ないかという問い合わせを頂きました。日の出や日の入り時刻を計算で求めることが出来れば、いろいろなことに応用出来るので面白いテーマです。

 ということで、調べてみたのですが、これ結構厄介でした。ネットにいろんな情報があるのですが、この話の教科書的な書物として、以下の本が有名なので図書館で借りてきました。

▼日の出日の入りの計算 (長沢 工著)
日の出日の入りの計算
 実はこの本にも私が求めているズバリの答えは書いてありませんでした。でも、読んでみると頭の中がかなり整理出来ました。日の出、日の入り時刻を求める方法には大きく以下の三つのアプローチがあるようです。なお、ここで前提としている課題は、日付と経緯度が判っている時に、日の出・日の入り時刻を知る方法です。

1) 基準地点の日の出・日の入りデーターを持っておき、経緯度の差から補正する方法。
2) 地球と太陽の相対位置を軌道計算から求める方法。(ユリウス暦日が出てきます)
3) 太陽位置を表す近似式を使う方法。

1) 項の方法は簡単な計算で出来そうです。元のデーターは適当に間引いて持っておき、使う時に補間すればあまりデーター量が増えなくても済みそうです。ただ、経緯度が大きく違うと計算誤差が大きくなりそうです。

2) 項の方法は精度の高い計算結果が得られますが計算がやっかいです。それに軌道要素は少しずつ変わるので、最新の理科年表の値を参照する必要があり、マイコンのプログラムとして埋め込むのは難しそうです。

3) 項の方法で使う近似式には大きく2種類あるようです。高精度な近似式(17次くらいの式です)ともう少し精度が低い近似式(3次くらいの式)です。高精度な近似式を使えば秒オーダーの精度が出るようですが、常に最新の近似式を使う必要があり、マイコンに埋め込むことは難しいです。ちなみに海上保安庁の海洋情報部から近似式で使う定数が毎年発表されています。

 一方で、精度の低い近似式ではいつも同じ係数で計算するのでマイコンのプログラムに埋め込むことが出来ます。ただ、問題はどのくらいの精度で計算出来るか判らない点です。また、Arduino の浮動小数点の値は 32ビットで計算されるのでPCと比べて精度が低く、誤差が出やすくなります。ということで、このあたりは実際にプログラムを作って確かめるしか無さそうです。

 そんなことで、3) 項の精度の低い近似式を使う方法で計算プログラムを作りました。作成に当たっては下記のサイトを参考にさせて頂きました。ありがとうございます。
http://k-ichikawa.blog.enjoy.jp/etc/HP/js/sunRise/srs.html  日の出・日の入時刻の年間変化(JavaScript版)
http://www.iot-kyoto.com/satoh/2016/01/22/post-99/  日の出時間と日没時間の計算方法~プログラム編

/*
Arduinoで日の出・日の入り時刻を計算
近似式を使って日の出・日の入り時刻を求める。
by ラジオペンチ, 2017/5/10, http://radiopench.blog96.fc2.com/
参考サイト
http://k-ichikawa.blog.enjoy.jp/etc/HP/js/sunRise/srs.html
http://www.iot-kyoto.com/satoh/2016/01/22/post-99/
*/

#include <math.h> // 数学ライブラリ https://www.arduino.cc/en/Math/H

#define M_PI 3.14159265358979323846 // πの値
#define DEG(a) ((a) * 180 / M_PI) // ラジアンを度に変換するマクロ
#define RAD(a) ((a) * M_PI /180) // 度をラジアンに変換するマクロ

float t1; // 日の入り時刻(単位:時)
float t2; // 日の入り時刻(単位:時)
float tm; // 南中時刻(単位:時)
float tL; // 昼の長さ(単位:時)

float Longitude = 139.7414; // 経度(東経を入力)
float Latitude = 35.6581; // 緯度(北緯を入力)

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

void loop() {
for (int n = 0; n < 365; n++) { // 日付連番 0から364日の範囲を計算
t1 = SunRiseTime(Longitude, Latitude, n); // 経緯度、日付連番から日の出時刻を求める
t2 = SunSetTime(Longitude, Latitude, n); // 経緯度、日付連番から日の入り出時刻を求める
tm = (t1 + t2) / 2; // 太陽南中時刻
tL = (t2 - t1); // 昼の時間
Serial.print(n + 1), Serial.print(", ");
Serial.print(t1, 4), Serial.print(", ");
Serial.print(t2, 4), Serial.print(", ");
Serial.print(tm, 4), Serial.print(", ");
Serial.print(tL, 4), Serial.println();
}
for (;;) {} // ここで停止
}

float SunRiseTime(float x, float y, int n) { // 日の出時刻を求める関数
float d, e, t;
y = RAD(y); // 緯度をラジアンに変換
d = dCalc(n); // 太陽赤緯を求める
e = eCalc(n); // 均時差を求める
// 太陽の時角幅を求める(視半径、大気差などを補正 (-0.899度) )
t = DEG(acos( (sin(RAD(-0.899)) - sin(d) * sin(y)) / (cos(d) * cos(y)) ) );
return ( -t + 180.0 - x + 135.0) / 15.0 - e; // 日の出時刻を返す
}

float SunSetTime(float x, float y, int n) { // 日の入り時刻を求める関数
float d, e, t;
y = RAD(y); // 緯度をラジアンに変換
d = dCalc(n); // 太陽赤緯を求める
e = eCalc(n); // 均時差を求める
// 太陽の時角幅を求める(視半径、大気差などを補正 (-0.899度) )
t = DEG(acos( (sin(RAD(-0.899)) - sin(d) * sin(y)) / (cos(d) * cos(y)) ) );
return ( t + 180.0 - x + 135.0) / 15.0 - e; // 日の入り時刻を返す
}

float dCalc(int n) { // 近似式で太陽赤緯を求める
float d, w;
w = (n + 0.5) * 2 * M_PI / 365; // 日付をラジアンに変換
d = + 0.33281
- 22.984 * cos(w) - 0.34990 * cos(2 * w) - 0.13980 * cos(3 * w)
+ 3.7872 * sin(w) + 0.03250 * sin(2 * w) + 0.07187 * sin(3 * w);
return RAD(d); // 赤緯を返す(単位はラジアン)
}

float eCalc(int n) { // 近似式で均時差を求める
float e, w;
w = (n + 0.5) * 2 * M_PI / 365; // 日付をラジアンに換算
e = + 0.0072 * cos(w) - 0.0528 * cos(2 * w) - 0.0012 * cos(3 * w)
- 0.1229 * sin(w) - 0.1565 * sin(2 * w) - 0.0041 * sin(3 * w);
return e; // 均一時差を返す(単位は時)
}
プログラムの解説
・Arduino IDE は1.8.1を使いました。
・10, 12行目 三角関数の acos を使うために念のために入れましたが、コメントアウトしても大丈夫でした。Arduino IDE に元々入ってるのかもしれません。
・21, 22行目 使う場所の経度、緯度を入れます。なお、デフォルトの値は暦の計算で使われる東京の座標です。
・30, 31行目 SunRiseTime と SunSetTime の関数で日の出と日の入り時刻を求めています。一つの関数でまとめて日の出/日の入り時刻を得るやり方もあり、その方が無駄な計算をしないで済みます。でも判り易さを優先してこのような作りにしてみました。なお、この関数一つの一回の実行時間は約 2.3ms でした(計算する値で多少変化します)。

▼実行結果(先頭部分)
計算結果表示
 一年分の日の出時刻、日の入り時刻、太陽南中時刻、昼の長さを出力します。

▼グラフ
シリアルプロッターによる結果表示
 Arduino IDE のシリアルプロッタでグラフにすると判り易いです。なお n をプロットすると見づらくなるので、34行目をコメントアウトしています。

 ということで日の出・日の入り時刻が計算出来るようになりました。気になるのはその精度です。

▼エクセルで計算精度確認
エクセルで精度確認

 計算精度確認のために、結果を天文台の暦のページから発表されている日の出、日の入り時刻と比較してみました。

 なお、比較に使ったデーターは、アナレンマを描く基本検討をやった時に入手した2016年のデーターです。この年は閏年なので366日あって比較対象としてはあまり良くないです。でも悪い条件で比較しておけばこれより悪くなることは無いでしょうからこのままいきます。(というのは言い訳で、データーを取り直すのが面倒です)

 長々と書いてきましたが、以下のグラフがこの記事の結論です。グラフの横軸は一年間を表し、縦軸が天文台の暦のページで発表されている日の出・日の入り時刻と、このプログラムの計算結果との差です。

▼日の出時刻の計算誤差
日の出時刻の誤差

▼日の入り時刻の計算誤差
日の入り時刻の誤差
 ほとんどのデーターは±60秒以内に入っています。日の出・日の入りがこれくらいの精度で計算出来ていれば問題無いと思います。天文台から発表される日の出・日の入り時刻は1分単位なので、プラスマイナス30秒の丸め誤差が入っているのですが、その様子が見えています。別の言い方をすると、プロットされた点は60秒の帯状の範囲に分布していますが、この帯の中心線が正確な日の出・日の入り時刻です。

◆まとめ
 Arduinoを使った日の出・日の入り時刻の計算プログラムが出来ました。実際に何かのアプリで使うためには、日付と時刻の情報が必要になり、RTCなどと組み合わせて使うことになると思います。でもともかく一歩前進です。

 日の出・日の入り時刻を求める計算では、三角関数の計算を沢山やらないといけません。これは8ビットのマイコンには重い仕事なので、実行時間がかなり長くなるのではないかと心配しました。しかし、やってみるとミリセカンドオーダーで計算されたのには驚きました。
 ちなみに昔の8ビットのマイコン、例えば Z80 に BASICインタープリターで同じ計算をやらせたら、どれくらいの実行時間になるか興味深いところです。たぶんこの10倍の時間を使っても済まない気がします。

アナログ気圧計2号機、運転開始

 アナログ表示の気圧計2号機が完成したので新しい電池を入れて運転を開始しました。この気圧計は電池交換無しで3年以上動くことを目標としているのですが、その間の電池の電圧がどのように変化するか興味深いところです。ということで、時々電圧を測って様子を見ることにしました。

 なお、アナログ気圧計2号機が完成したので、アナログ気圧計(LPS25H) のカテゴリを作り、関連記事をそこにまとめました。

▼気圧計運転開始
稼働開始

▼入れた電池はダイソーのアルカリ電池
ダイソーの電池
 微小電流で使うならマンガン電池の方が適しているという話もあるのですが、絶対的な容量が大きい(と思われる)アルカリ電池で行くことにしました。電池は国産の有名メーカー品を使うことも考えたのですが、ダイソーの電池で安く済ましました。何しろ5本で100円なので、もはやマンガン電池を使う理由は見つからないです。電池がこんなに安くなると、充電出来るニッケル水素も最近はあまり使わなくなりました。

 なお表示によると、この電池の使用推奨期限は7年で、2024年1月がその期限になっています。

▼初期電圧を記録
初期電圧測定
 せっかくの機会なので、電池3本に番号をマーキングして、電圧を記録しておきました。ついでにリファレンス用の電池の電圧も記録しておきます。電池電圧は No1:1.6449V, No2:1.6434V, No.3:1.6446V, Ref:1.6441Vでした。最近の電池は初期電圧がこんなに高いんですね。

▼組み込み完了
アナログ気圧計2号機
 ケースのフタの裏に回路図を印刷した紙を折りたたんで貼り付けておきました。昔のラジオはみんな回路図が貼り付けてありましたが、それと同じでメンテする時に役に立つはずです。

▼フタを閉じて使用開始
背面(リファレンス電池)
 リファレンスの電池は紛失しないように時計のムーブメントの電池ボックスに入れておきます。この電池の目的は、無負荷で何もしないで保存した電池の電圧の変化を確認するためです。当然ですが、この電池ボックスには何の配線もありません。

 なお、ケースを開けないで簡単に電池電圧の測定が出来るように、ケースの側面に小さな穴を開けました。この穴にテスターの棒を突っ込んで電圧測定を行います。

▼電池電圧推移
電池電圧推移
 動かし始めてまだ10日しか経ってませんが、電池電圧の推移グラフです。まだ4.9Vくらいあります。なお、青の点線は一次回帰直線です。

 この気圧計は電池電圧が3.4Vまで下がるとバッテリーアラームになって動作停止する仕掛けになっています。このペースで電圧が下がり続けると、回帰直線の式の予想では 380日あたりで3.4Vになります。ということは、これ3年持たないじゃん!と言われそうです。でも、たぶん大丈夫です。電池電圧が4.5V (1.5V x 3本) くらいになると、電池本来のパワーが出て来て電圧の低下はずっと少なくなるはずです。

◆まとめ
 本当に3年持つかどうか、こればっかりはやってみないと判りません。ということで、時々様子をレポートしたいと思いますのでお楽しみに。しかし3年後でも私のモチベーションが維持出来ているか、の方が問題かも知れません。

アナログ気圧計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年間動くか注目していきたいと思います。まずは新品の電池を入れて動かし始めますが、様子は時々このブログで紹介したいと思いますのでお楽しみに。
カレンダー
04 | 2017/05 | 06
- 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 31 - - -
プロフィール

ラジオペンチ

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

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