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

マウナケア山頂に自作高度計を持っていった

 久しぶりのブログの更新です。このところ電子工作をやっていないので、ネタ切れ状態です。でも、ちょうど良い話題があったので紹介します。 

 先日ハワイ島に旅行に行ったのですが、その際マウナケアの山頂へ行くツアーに参加しました。で、せっかくのチャンスなので自作の気圧高度計を持って行って、ちゃんと動くか試してみました。

 持って行ったのは気圧センサーのLPS25Hを使ったデジタル表示の高度計です。

 マウナケアの山頂ツアーは4000mを越える高さまで車で連れてってもらえますが、途中に未舗装道路もあるので4WDのワゴンが使われます。

▼途中の道端で小休止
標高1967.3m@サドルロード
 ここはアプローチになるサドルロードという道で標高は1967.3mです。背後はツアーのワゴン車です。こんなふうに途中で休憩を入れながら高度順化していきます。この後でサドルロードを離れ、一気に山道を登っていくことになります。

 途中でオニズカポイントで休憩と晩御飯のお弁当を食べ、その後山頂へ向かいます。

▼山頂付近の車中にて、
標高4115.4m@マウナケア山頂付近
 出ました!標高4115.4m。気圧高度計はちゃんと動いています。なお、マウナケアの山頂は4205mですが、そこまで道路が通っていないので車では行けません。確か聖地なので勝手に歩いて行ってはダメな場所だったと思います。

 せっかくなので山頂からの絶景を、

▼日没後の夕暮れ時
マウナケア山頂の夕暮れ
 正面の雲海に太陽が沈んで暗くなり始めた頃。右下はまだ明るく左上は夜。つまり昼と夜の境目が見えているのだと思います。空の中の明るい光は月です。

 右下の黒いシルエットは天文台。広角レンズのせいで傾いて写っていますが、右から4つ目がすばる天文台です。

 以下は山頂から少し下にある駐車場からの撮影です。

▼南方向の星空
南方向
 右下の黒い稜線は雲海の上のマウナケア。稜線の延長にかすかに赤く見えるのは、キラウエア火口の溶岩が雲を照らしているためです。

 マウナケアとキラウエアの中間付近の低い空に南十字星が写っています。

▼北方向の星の周回写真
北方向、星の周回写真
 露出20秒を12枚比較明合成したものです。充分な枚数の撮影が出来なかったので星の軌跡の長さが少ないですが、北極星の高度がとても低いことがよく判ります。

 ということで、めったに出来ない体験をしてきました。山頂付近は空気がとても薄くて、乱暴に動くと体がおかしくなってしまいそうな場所でした。

LPS25Hを使った気圧高度計、ソフト解説

 一応は製作完了した気圧高度計ですが、まれに気圧の測定結果が高くなる現象が発生していました。いろいろ調べた結果、なんとか不具合が解決出来ました。

 ということで、未来の自分への連絡を兼ねて、プログラム(スケッチ)の要点を説明します。

 なお、このプラグラムはArduino で ATmega328Pで動かすことを想定しています。また、このスケッチはArduino UNOの状態(クロック=16MHz)でCPUに書いて、クロック8MHzで動かしているので、実際の動作速度は半分になっています。
/*
気圧センサ LPS25Hを使った高度計
センサーと液晶はSPIインターフェイス
液晶はaitendoのFSTN液晶、モノクロ、128x64、SPI(UC1701)
海面気圧はロータリーエンコーダーで設定しEEPROMに保存し、
次回起動時は前回の設定値を使う。
スケッチサイズ27880バイト, 内部8MMHz用にスピード調整
2015/4/16 ラジオペンチ、http://radiopench.blog96.fc2.com/
 */

#include <U8glib.h>                   // FSTN液晶
#include <SPI.h>
#include <Wire.h>
#include <MsTimer2.h>                 // ロータリーエンコーダー読取り用割込み
#include <EEPROM.h>

#define enc_A 2                       // ロータリーエンコーダ A相
#define enc_B 3                       //            B相
#define LED 4                         // LEDピン(Powと状態表示)
#define LPS25H_cs 8                   // LPS25HのCS(Chip SElect)

float p0, hpa, qnh, elev;
float Pdiff = -4.9;                   // 気圧補正値(センサ誤差の補整値)
unsigned int p0x10;                   // 海面気圧(p0 hPa)の10倍の値をint型で保持
unsigned int p0_EEPROM;
volatile int diff = 0;
char EEPflag = ' ';                   // EEPROM 未書き込みフラグ

U8GLIB_MINI12864 u8g(10, 9);          // u8glibをハードウエアSPIで動かす,(UC1701)

void setup(void) {
  pinMode(enc_A, INPUT);                  // ロータリーエンコーダーA
  digitalWrite(2, HIGH);                  // プルアップ
  pinMode(enc_B, INPUT);                  // ロータリーエンコーダーB
  digitalWrite(3, HIGH);                  // プルアップ
  pinMode(LED, OUTPUT);                   // LED表示(HIGHでLED点灯
  digitalWrite(LED, HIGH);                // 電源ON表示

  pinMode(LPS25H_cs, OUTPUT);             // 気圧センサーのCS
  digitalWrite(LPS25H_cs, HIGH);

  SPI.begin() ;                           // SPI開始
  SPI.setBitOrder(MSBFIRST) ;             // ビットオーダー
  SPI.setClockDivider(SPI_CLOCK_DIV4) ;   // SPI CLK倍率設定(16MHz/4)
  SPI.setDataMode(SPI_MODE3) ;            // クロックとデータ定義

  p0_EEPROM = EEPROM.read(0) * 256 + EEPROM.read(1);   // EEPROMから海面気圧を読み出し
  if ( (p0_EEPROM > 11000) || ( p0_EEPROM < 9000)) {   // 900~1100hPaの範囲外だったら
    p0_EEPROM = 10130;                                 // 1013hPaに修正
    EEPROM.write(0, p0_EEPROM / 256);
    EEPROM.write(1, p0_EEPROM % 256);
  }
  p0x10 = p0_EEPROM;                      // 海面気圧の値を設定

  u8g.setContrast(175);                   // 液晶のコントラスト設定
  LPS25H_setup();                         // LPS25Hに測定条件設定

  delay(100);                            // 状態が落ち着くまでちょっと待つ
  MsTimer2::set(1, timerIRQ);            // ロータリーエンコーダ読取り用の割込み
  MsTimer2::start();                     // 開始
}

void loop(void) {
  hpa = LPS25H_mesure();                 // 気圧測定
  p0 = rotary_value() / 10.0;            // ロータリーエンコーダで海面気圧設定
  qnh = p0 * 0.029536;                   // QNH換算値算出

  elev = 153.8 * (15 + 273.2) * (1 - pow(hpa / p0, 0.1902)); // 標高計算

  u8g.firstPage();                       // ディスプレイ ループ開始
  do {
    lcdDisp();
  }
  while ( u8g.nextPage() );
  delay(20);                           // 8MHzクロック用設定(16MHzなら100)
}

void LPS25H_setup() {            // 気圧センサ測定条件設定
  LPS25H_write(0x10, 0x05);      // アベレージング回数=気圧32回、温度16回
  LPS25H_write(0x20, 0x84);      // PD=1, OneShot Mode, BDU=1
  LPS25H_write(0x2E, 0xDF);      // FIFO移動平均モード、サンプル数=32
  LPS25H_write(0x21, 0x40);      // FIFO=Enable
}

float LPS25H_mesure() {          // センサーから気圧読み出し
  float p;
  unsigned long val, data;
  MsTimer2::stop();              // タイマー割込み禁止
  LPS25H_write(0x21, LPS25H_read(0x21) | 0x01);  // 変換スタートビットを立て
  while ((LPS25H_read(0x21) & 0x01) == true) {   // 変換完了まで待つ
  }
  delayMicroseconds(20);                         // タイミング調整(5/15追加)
  val = LPS25H_read(0x28);    // read XL
  data = LPS25H_read(0x29);   // read L
  data = data << 8;
  val = val | data;
  data = LPS25H_read(0x2A);   // read H
  data = data << 16;
  val = val | data;

  p = val / 4096.0;           // 換算係数を掛けてhpaを計算
  p = p + Pdiff;              // センサ誤差補正
  MsTimer2::start();          // タイマー割込み再開
  return p;                   // 気圧の値をhPa単位で返す
}

void LPS25H_write(byte adr, byte data) {
  digitalWrite(LPS25H_cs, LOW);       // LPS25H CS ON
  SPI.transfer(adr);
  SPI.transfer(data);
  digitalWrite(LPS25H_cs, HIGH);      //  LPS25H CS off
}

byte LPS25H_read(byte adr) {
  byte ret;
  adr = adr | 0x80;                  // 最上位ビット=1でWrite
  digitalWrite(LPS25H_cs, LOW);      // LPS25H CS ON
  SPI.transfer(adr);
  ret = SPI.transfer(0);
  digitalWrite(LPS25H_cs, HIGH);     //  LPS25H CS off
  return ret;
}

void lcdDisp() {                                  // 液晶表示
  int x;

  if ( elev >= 1000.0) {                          // 桁数で標高表示位置を調整
    x = 10;
  } else if ( elev >= 100.0) {
    x = 20;
  } else if ( elev >= 10.0) {
    x = 30;
  } else if ( elev >= 0.0) {
    x = 40;
  } else if ( elev > -10.0) {
    x = 30;
  } else if ( elev > -100.0) {
    x = 20;
  } else {
    x = 10;
  }

  u8g.setFont(u8g_font_helvB24);                  // 大きい文字で
  u8g.setPrintPos(x, 30); u8g.print(elev, 1);     // 標高表示
  u8g.setFont(u8g_font_unifont); u8g.print("m");

  u8g.setPrintPos(0, 48); u8g.print("Px=");
  u8g.print(hpa, 2); u8g.print("hPa");            // 気圧測定値
  u8g.print(" "); u8g.print(EEPflag);             // EEPROM書き込み状況表示

  u8g.setPrintPos(0, 63); u8g.print("Po=");
  u8g.print(p0, 1); u8g.print("(");               // 海面気圧 hPa
  u8g.print(qnh, 2); u8g.print(")");              // 海面気圧 QNH
}

unsigned int rotary_value() {                     // ロータリーエンコーダの値を海面気圧に反映
  static int n = 0;
  noInterrupts();
  p0x10 += diff;                                  // 海面気圧設定値を反映
  diff = 0;
  interrupts();

  p0_EEPROM = EEPROM.read(0) * 256 + EEPROM.read(1);
  if (p0x10 == p0_EEPROM) {                           // EEPROMの値と同じだったら
    n = 0;                                            // カウンタリセット
    EEPflag = ' ';                                    // 表示はブランク
    return p0x10;                                     // 海面気圧返してすぐに抜ける
  }
  else {
    n++;
    EEPflag = '*';                                    // 未書き込み表示文字 (*) セット
    if (n == 50) {                                    // 違う状態が10秒続いたら(0.2秒×50回=10秒)
      MsTimer2::stop();                               // タイマー割込み停止
      EEPROM.write(0, p0x10 / 256);                   // EEPROMを現在値に書き換え
      EEPROM.write(1, p0x10 % 256);
      MsTimer2::start();                              // タイマー割込み再開
      n = 0;
    }
    return p0x10;                                     // どちらにしても海面気圧を返して抜ける
  }
}

void timerIRQ() {             // MsTimer2割込み処理(ロータリーエンコーダ読取り)
  static byte bp = 0;                 // エンコーダーの状態記録バッファ

  bp = bp << 1;
  if (digitalRead(enc_A) == HIGH) {   // A相の状態を
    bp |= 0x01;                       // bpの末尾に記録
  }
  bp = bp << 1;
  if (digitalRead(enc_B) == HIGH) {   // B相の状態を
    bp |= 0x01;                       // bpの末尾に記録
  }

  bp &= 0x0F;                         // 下位4ビットを残して上位をクリア
  //  if (bp == 0b0111) {               // このビットパターンと一致していたら
  if ((bp == 0b0111) | (bp == 0b1000)) {   // 倍クリックタイプの場合はこちら
    diff ++;                          // インクリメント
  }
  //  if (bp == 0b1011) {               // このビットパターンと一致していたら、
  if ((bp == 0b1011) | (bp == 0b0100)) {    // 倍クリックタイプの場合はこちら
    diff --;                          // デクリメント
  }

  if (diff != 0) {                    // 値がゼロでなかったら
    digitalWrite(LED, LOW);           // ロータリーエンコーダーLED消灯
  }
  else {
    digitalWrite(LED, HIGH);          // ロータリーエンコーダーLED点灯
  }
}

1.使ったライブラリ
 11~15行の#includeで宣言していますが、G-LCD用にU8glib。センサーのLPS25HをSPIで使うためにSPIとWire。ロータリーエンコーダーに状態読み込みの割り込み用にMsTimer2。海面気圧の保存用にEEPROMのライブラリを使っています。プログラムサイズが27.88kバイトと大きいのは、いろんなライブラリを使っていることと、u8glibが使用するフォントデーターが大きいためです。
 気圧センサーのLPS25HはI2Cインターフェイスで使われる場合が多いと思います。でもG-LCDがSPIインターフェイスなのでこれに合わせてLPS25HをSPIインターフェイスで使ってみました。

2.気圧センサLPS25Hの使い方
 初期設定を LPS25H_setup() の中で行っています。センサーをフリーランさせておいて、プログラムは非同期で読む方法もありますが、ここではプログラムで毎回測定開始を指示させるように設定しています。設定内容の詳細はプログラム中のコメントをご覧下さい。

 気圧の測定は LPS25H_measure() で行っています。この関数の中でLPS25Hの測定開始フラグを立てて測定を開始させ、測定完了を確認した後に3バイトの気圧データーを読み出しています。92行の delayMicroseconds(20); は後で追加したもので、これが無いとまれに変な値が返ってきました。ちなみにLPS331でも同じような現象が出てました。データーシートを隅々まで読むとそのあたりの使い方の注意事項が書いてあるのかも知れません。

3.ロータリーエンコーダーの読み込み
 MsTimer2で1ms(2ms)周期で発生させた割込みを使い、 timerIRQ() 関数でエンコーダの回転方向の検出を行っています。検出はビットパターンの照合方式で行っており、エンコーダーが動いた場合は特有のビットパターンになるので回転の検出が出来ます。なお、ロータリーエンコーダーが静止している状態では0b0000か0b1111のパターンになるので照合しても全くヒットしないので何も起こりません。

 ところで、私の使ったエンコーダーはクリックストップの位置で、A相とB相の両方がONとOFFの状態が交互に発生します。こういう仕様のエンコーダーなので、プログラムの197行と201行で回転の検出を行っています。秋月で売っているエンコーダーはクリック位置で接点がOFFになるタイプらしいので、この場合は196と200行を使うとうまく動くはずです。
 ちなみに接点の状態を読むには抵抗でプルアップしてレベルを見ることになりますが、クリックストップ位置で接点がOFFとなる仕様のスイッチなら、無駄な電流が流れないので省エネの点では有利になると思います。

4.EEPROM書き込み
 海面気圧の設定値はEEPROMに保存していますが、このEEPROMの値の修正は現在の設定値と違う状態が10秒経ったら自動的に行っています。この機能の動作状態を、液晶のPx=1013.00hPaの後ろに’*’を表示することで行っています。ロータリーエンコーダーを廻すことで、海面気圧の値がEEPROMと違う状態になったら * マークが表示され、その状態が10秒続くとEEPROMの値の更新が行われ * マークが消えます。

◆まとめ
 ということで、だらだらと長い記事になってしまってすみません。LPS25Hを使った製作記事はまだ少ないし、SPIモードとなると更に少ないと思います。これが何かの参考になれば幸いです。

LPS25Hを使った気圧高度計、ケースに入れて製作完了

 STマイクロの気圧センサLPS25Hを使った気圧高度計作りに取り組んでいましたが、とりあえず完成しました。

▼完成した気圧高度計
気圧高度計
 もう少し小型のケースに入れたかったのですが、私の実力ではこれくらいが限界です。

 グラフィック液晶を使って高度(標高)を大きなフォントで表示させています。ちなみにこのG-LCDはaitendoの今年(2015年)の福袋に入っていた物です。

 右側のつまみはロータリーエンコーダーで、これをカリカリ廻して海面気圧(P0)を入力します。P0の値はQNHでも表示しているので飛行場のATISを聞いて入力することも出来ます。

▼液晶画面
FSTN液晶
 これは試作時に撮った写真ですが、こんな表示になっています。

▼回路図(クリックで別窓に拡大)
LPS25H気圧高度計の回路図

 試作の頃の回路と比べて以下を改良しました。
 ・CPUを3.3Vで動かすことでレベル変換回路を省略
 ・CPUのクロックは内部8MHzを使って外付けの水晶を省略(要ヒューズ設定)

 3.3V化に伴い電源も簡単になって、電池一本と昇圧コンバーターだけで済ませることが出来ました。今回使った秋月の3.3V出力コイル一体型昇圧DCDCコンバーター(M-08618)は小型で軽負荷時の消費電力も小さいのでなかなか具合が良いです。

 LPS25H気圧高度計のスケッチはこちら
 
▼基板
基板
 コネクタで基板を本体から分離出来るようにしました。左の6ピンのモジュールがDCDCコンバーター、右の8ピンのモジュールが気圧センサー(LPS25H)、手前の28ピンのICがCPU(ATmega328P)です。

▼配線面
はんだ面
 赤色が電源、裸線と透明がGND、青色のが信号で配線しています。

▼ケース内の配線中
ケース内配線中
 完成まであと少しの状態です。まだ液晶は固定していません。ケースはタカチのSW-75です。(ケースサイズは 75x50x30mm)

▼製作完了
配線完了
 液晶はホットボンドで固定しています。外す必要が発生した場合は、、その時に考えます。w

 3.3V/8Mhz駆動にして消費電流が大きく減ったので単4電池一本で動かすことが可能になりました。電池電流はバックライト点灯無しで約30mAだったので、20時間以上は動くはずです。

▼操作スイッチまわり
LPS25H気圧高度計の外観
 右側面にスイッチを集めてあります。写真左から順に
 ・電源/モードスイッチ (OFF/ON/バックライトON)。
 ・電源表示ランプ(ロータリーエンコーダ操作で点滅します)
 ・海面気圧入力用ロータリーエンコーダ

 コンパクトなケースに気圧高度計として必要な最低限の機能を入れてみました。これをポケットに入れてあちこち歩き回ると、高度の変化と、天候の変化に伴う気圧の変化が直接判って面白いです。
 
 なお、ロータリーエンコーダーをいじった場合、ごくまれに気圧の測定結果が30hPa程度上がってしまう現象が出ています。何も操作しなければこの現象は発生しないし、電源を入れ直せば直るので致命的では無いですが、あまり面白くありません。対策方法が判ったら修正記事を書こうと思います。
カレンダー
05 | 2023/06 | 07
- - - - 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コード