一応は製作完了した気圧高度計ですが、まれに気圧の測定結果が高くなる現象が発生していました。いろいろ調べた結果、なんとか不具合が解決出来ました。
ということで、未来の自分への連絡を兼ねて、プログラム(スケッチ)の要点を説明します。
なお、このプラグラムは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モードとなると更に少ないと思います。これが何かの参考になれば幸いです。