// // 気圧ロガー // by ラジオペンチ http://radiopench.blog96.fc2.com/ // 2012/08/20 初版作成 // 2012/10/08 時刻合わせ機能追加 // 2012/12/03 オフセット値入力機能追加 // 2012/12/09 気圧トレンドグラフ、フリップ表示機能追加 // スケッチサイズ26.718kByte #include #include #include #include #include File myFile; LiquidCrystal lcd(9, 8, 5, 4, 3, 2); // デフォルトから変更 12 ->9 11->8 // RTC関係の変数 char dtString[20]="yyyy/mm/dd hh:mm:ss"; // テンプレート用文字列で初期化 byte RtcADR = 0xA2 >> 1; // RTCのアドレス // 気圧計関係の変数 const int addresPsensor = 0x60; // 気圧計のI2Cアドレス float a0, b1, b2, c12, c11, c22; float Pha, Temp; int tempOffset, pressOffset; unsigned int Padc; unsigned int Tadc; float sumPadc; float sumTadc; float avPadc; float avTadc; // 操作ボタン定義 int entB = 16; // EnterボタンのピンNo. 注;アナログピンをデジタルで使う時はA2とは書かない int selB = 17; // SelectボタンのピンNo. char fileName[11] = "yymmdd.csv"; // ファイル名 // 簡易グラフ作成用変数 int trendP[12]; // 気圧トレンド記録 int trendN=0; // 記録されたトレンド数 boolean dispFlipFlag=false; boolean dispTrendFlag=false; int FlipN=0; // FLIP表示インターバルカウンタ void setup() { unsigned int x; pinMode(6, INPUT); // RTCの /INT信号を接続 digitalWrite(6, HIGH); // オープンドレインなのでプルアップ pinMode(13, OUTPUT); // 動作表示LED pinMode(entB, INPUT); // Enterボタン(ENT) pinMode(selB, INPUT); // Selectボタン(SEL) digitalWrite(entB, HIGH); // プルアップ digitalWrite(selB, HIGH); // プルアップ Serial.begin(9600); lcd.begin(16, 2); Rtc.begin(); // RTCを開始 // 開始時にスイッチの状態を読んで各種機能を起動 if((digitalRead(entB)==LOW) & (digitalRead(selB)==HIGH)){ // ENTだけ押されていたら tempOffsetChange(); // 気温オフセット変更(この関数は無限ループ) } if((digitalRead(entB)==HIGH) & (digitalRead(selB)==LOW)){ // SETだけ押されていたら pressOffsetChange(); // 気圧オフセット変更(この関数は無限ループ) } if((digitalRead(entB)==LOW) & (digitalRead(selB)==LOW)){ // 両方押されていたら ClockSet(); // 時計合わせ } lcd.clear(); SetRtcTimer(); // 定周期タイマーの設定 // SDカードの初期化 if (!SD.begin(10)) { Serial.println("SD initialization failed!"); lcd.print("SD failed. Halt!"); for(;;); // ここでHalt } Serial.println("SD initialization done."); lcd.print("SD ok!"); // 気圧計の初期設定 // EEPROM.write(1, 27); // 温度補正値書き込み // EEPROM.write(3, 75); // 気圧補正値 x = EEPROM.read(0); // EEROMから温度の補正値読み出し unit=0.1℃ x = x << 8; tempOffset = x; //上位8ビット x = EEPROM.read(1); tempOffset = tempOffset | x; // 下位8ビット x = EEPROM.read(2); // 気圧の補正値 x = x << 8; pressOffset = x; // 上位 x = EEPROM.read(3); pressOffset = pressOffset | x; // 下位 Serial.print(tempOffset); Serial.print(", "); Serial.println(pressOffset); defGaiji(); // 液晶の外字を定義 delay(1000); // 気圧計が立ち上がるまで待つ Wire.beginTransmission(addresPsensor); Wire.write(0x04); // Read coefficient data Wire.endTransmission(); Wire.requestFrom(addresPsensor, 12); // Request 12 bytes if (Wire.available()) { a0 = read_coefficients(16, 3, 0); b1 = read_coefficients(16, 13, 0); b2 = read_coefficients(16, 14, 0); c12 = read_coefficients(14, 13, 9); c11 = read_coefficients(11, 10, 11); c22 = read_coefficients(11, 10, 15); } Serial.println("1000 * coefficient a0, b1, b2, c12, c11, c22 "); Serial.print(a0*1000); Serial.print(", "); Serial.print(b1*1000); Serial.print(", "); Serial.print(b2*1000); Serial.print(", "); Serial.print(c12*1000); Serial.print(", "); Serial.print(c11*1000); Serial.print(", "); Serial.println(c22*1000); Rtc.available(); // 時刻を取得 convString(); // RTCの時刻(BCD)を文字列に変換してdtStringに格納 fileName[0] = dtString[2]; // y ファイル名の決定 fileName[1] = dtString[3]; // y fileName[2] = dtString[5]; // m fileName[3] = dtString[6]; // m fileName[4] = dtString[8]; // d fileName[5] = dtString[9]; // d Serial.print("File Name = "); Serial.println(fileName); myFile = SD.open(fileName, FILE_WRITE); // setupで取得したファイル名を使用 myFile.println(); // 改行 myFile.print("Record Start. tempOffset="); myFile.print(tempOffset); myFile.print(" pressOffset="); myFile.println(pressOffset); myFile.close(); } void loop() { float sumP, sumT; long outP, outT; // 表示用データ int n; sumP = 0; sumT = 0; if(digitalRead(entB)==LOW){ // ENTERボタンが押されていたら dispFlipFlag=!dispFlipFlag; // 切り替え表示フラグ反転 lcd.clear(); // 動作内容表示 lcd.print("Disp mode change"); lcd.setCursor(0, 1); if(dispFlipFlag==true){ lcd.print("to FLIP"); } else{ lcd.print("to FIX"); } } // 動作中にボタンが押された場合の処理 if(digitalRead(selB) == LOW){ // SELECTボタンが押されていたら dispTrendFlag=!dispTrendFlag; // 表示モードフラグ反転 lcd.clear(); // 動作内容表示 lcd.print("Display"); lcd.setCursor(0,1); if(dispTrendFlag == true){ lcd.print("PRESSURE trend"); } else{ lcd.print("NORMAL data"); } } if(dispFlipFlag == true){ // 表示自動切り替えフラグがONなら FlipN++; if(FlipN >=2){ // 2秒に一回フリップ dispTrendFlag = !dispTrendFlag; // 表示フラグ反転 FlipN=0; } } waitTimer(); // RTCの /INT信号を待つ for( n=0; n < 10; n++){ // 安定待ちのため10回ダミーリード sensData(); // 気圧計のデータを読む(ダミー) } // データーの読み出し、アベレージング、オフセット付加 sumPadc=0; sumTadc=0; for(n=0; n<100; n++){ // バラツキが大きいので100回アベレージング sensData(); // 気圧計のデータを読む(本番) sumP = sumP + Pha; sumT = sumT + Temp; sumPadc = sumPadc + Padc; sumTadc = sumTadc + Tadc; } outT = int(sumT /10 + 0.5) + tempOffset; // 小数点以下1桁の値に丸める outP = int(sumP /10 + 0.5) + pressOffset; avPadc = sumPadc / 100; avTadc = sumTadc / 100; Rtc.available(); // 時刻を取得 convString(); // RTCの時刻(BCD)を文字列に変換してdtStringに格納 Serial.print(dtString); // シリアルに書き出し Serial.print(", "); Serial.print(outT); Serial.print(", "); Serial.println(outP); //  SDカードに書き込み // if(dtString[18] == '0' ) { // 10秒毎に記録(デバッグ用) if( ( (dtString[15] == '0') | (dtString[15] =='5') ) & (dtString[17] == '0') & (dtString[18] == '0') ) { // 5分00秒か0分00秒に記録 myFile = SD.open(fileName, FILE_WRITE); // setupで取得したファイル名を使用 myFile.print(dtString); myFile.print(", "); if(outT<0){ myFile.print("-"); } myFile.print(abs(outT) / 10); // 小数点以上の値 myFile.print("."); myFile.print(abs(outT) % 10); // 10で割った余り myFile.print(", "); myFile.print(outP/10); // 小数点以上の値 myFile.print("."); myFile.print(outP % 10); // 10で割った余り myFile.print(", "); myFile.print(avTadc,3); // 補正前の温度センサの値の100回平均値 myFile.print(", "); myFile.println(avPadc,3); // 補正前の圧力センサの値の100回平均値 myFile.close(); } // トレンドグラフ用にデータを保存 // if( dtString[17]=='0' & dtString[18]=='0'){ // 1分毎に気圧を記録 (デバッグ用) if( dtString[14]=='0' & dtString[15]=='0' & dtString[17]=='0' & dtString[18]=='0'){ // グラフ用に1時間毎に気圧を記録 trendN++; // データ数カウンタインクリメント if(trendN >10){ // 但し上限は10まで trendN = 10; } if(trendN >= 2){ // 2つ目以降なら for(n = trendN-1;n>0; n-- ){ trendP[n] = trendP[n-1]; // データを後ろにずらして } } trendP[0]=outP; // 先頭に最新データを格納してグラフ表示データ作成完了 } // 液晶表示 if(dispTrendFlag==true){ // トレンド表示フラグがONで if(trendN >=2){ // データが2個以上そろっていたら trendWrite(trendN); // トレンドグラフ表示 } else{ lcd.clear(); lcd.print("Wait 2Hr"); // まだデーターがそろっていないよ } } else{ // トレンド表示フラグがOFFなので通常標示 lcd.clear(); // lcd.print(" "); // 液晶に書き出し 先頭に1文字ブランク for( n=5; n <= 18; n++){ // 全部入らないので年は省略 lcd.print(dtString[n]); } lcd.setCursor(0 , 1); // 2行目 dispPress(outP); // 気圧表示 lcd.print("hPa "); if(outT < 0){ // 気温がマイナスなら lcd.print("-"); // 符号を表示 } else { lcd.print(" "); } if(abs(outT)<100){ // 値が10℃以下なら lcd.print(" "); // 先頭にスペース } lcd.print(abs(outT) /10); lcd.print("."); lcd.print(abs(outT) % 10); lcd.write(8); // 温度の単位(℃)表示。本当は0を指定すべき(後日修正要す) lcd.print(" "); // 念のためにゴミ消し } } // Loopの末尾 void convString(){ // RTCの2バイトのBCDを文字に変換し時刻の文字列を作る byte x; dtString[0]='2'; dtString[1]='0'; x = Rtc.years(); dtString[2] = upper2chr(x); dtString[3] = lower2chr(x); x = Rtc.months(); dtString[5] = upper2chr(x); dtString[6] = lower2chr(x); x = Rtc.days(); dtString[8] = upper2chr(x); dtString[9] = lower2chr(x); x = Rtc.hours(); dtString[11] = upper2chr(x); dtString[12] = lower2chr(x); x = Rtc.minutes(); dtString[14] = upper2chr(x); dtString[15] = lower2chr(x); x = Rtc.seconds(); dtString[17] = upper2chr(x); dtString[18] = lower2chr(x); } char upper2chr(byte x){ // 上位をAsciiコードに変換 return (x >> 4) + 0x30; } char lower2chr(byte x){ // 下位をAsciiコードに変換 return (x & 0x0f) + 0x30; } void waitTimer(){ // RTCの /INT信号を待つ while(digitalRead(6) == HIGH){ // HIGHなのでLOWになるまで待つ } while(digitalRead(6) == LOW){ // 再びHIGHになるまで待つ digitalWrite(13, HIGH); // LED ON } digitalWrite(13, LOW); // LED OFF } void SetRtcTimer(){ // 定周期タイマー割り込みの設定 Wire.beginTransmission(RtcADR); Wire.write(0x0e); // タイマー停止 Wire.write(0x00); Wire.endTransmission(); Wire.beginTransmission(RtcADR); Wire.write(0x01); // コントロールレジスタ-2の設定 Wire.write(0x11); // TI/TP=on, TIE=on Wire.endTransmission(); Wire.beginTransmission(RtcADR); Wire.write(0x0f); // タイマー設定値 Wire.write(0x01); // 1回毎 Wire.endTransmission(); Wire.beginTransmission(RtcADR); Wire.write(0x0e); // タイマースタート Wire.write(0x82); // TEビットon, カウンタソースクロックは1秒を設定 Wire.endTransmission(); } void sensData () { Wire.beginTransmission(addresPsensor); Wire.write(0x12); // Start both conversions(Pressure and Temperature) Wire.write(0x01); Wire.endTransmission(); delay(3); Wire.beginTransmission(addresPsensor); Wire.write((uint8_t)0x00); // Read pressure and temperature Wire.endTransmission(); Wire.requestFrom(addresPsensor, 4); // Request 4 bytes if(Wire.available()) { Padc = read_adc(); Tadc = read_adc(); int signedTadc = Tadc; // float Pcomp = a0 + (b1 + c11 * Padc + c12 * Tadc) * Padc + (b2 + c22 * Tadc) * Tadc; // 22976 float Pcomp = a0 + (b1 + c12 * Tadc) * Padc + (b2 * Tadc); // c11 とc22はゼロなので計算省略(84バイト節約) Pha = Pcomp * 650 / 1023 + 500; Temp = 25 - (signedTadc - 472) / 5.35; } delay(1); } float read_coefficients(int total_bits, int fractional_bits, int zero_pad) { unsigned char msb, lsb; msb = Wire.read(); lsb = Wire.read(); Serial.print("mbs = "); Serial.print(msb); Serial.print(", lsb = "); Serial.println(lsb); return ((float) ((msb << 8) + lsb) / ((long)1 << (16 - total_bits + fractional_bits + zero_pad))); } unsigned int read_adc() { unsigned char msb, lsb; msb = Wire.read(); lsb = Wire.read(); return (((unsigned int)msb << 8) + lsb) >> 6; } // 時計の時刻合わせ void ClockSet(){ int n, nen, tsuki, dayCount; lcd.setCursor(0,0); lcd.print("Clock set mode"); // モード入りメッセージ while(digitalRead(entB) == LOW){ // Clock EnterスイッチがOFFになるまで待つ } delay(30); lcd.setCursor(0,0); lcd.print("SEL=Val,ENT=Next"); Rtc.available(); // 現在の時刻を取得 convString(); // RTCの時刻(BCD)を文字列に変換してdtStringに格納 lcd.setCursor(0,1); for(n=0; n<=15;n++){ lcd.print(dtString[n]); } //データ位置とmin,max値を指定して数値設定プログラムを呼ぶ setV(3, 12, 32); // 年(2012〜2032年まで) setV(6, 1, 12); // 月 // 日は月の大小と閏年を反映して呼ぶ nen = (dtString[2] & 0x0f)*10 + (dtString[3] & 0x0f); tsuki = (dtString[5] & 0x0f)*10 + (dtString[6] & 0x0f); dayCount =31; if(tsuki == 4 | tsuki== 6 | tsuki == 9 | tsuki == 11){ dayCount =30; } if(tsuki == 2){ // 2月の処理 dayCount = 28; if((nen % 4) == 0){ dayCount =29; // うるう年 } } setV(9, 1, dayCount); // 日 setV(12, 0, 23); // 時 setV(15, 0, 59); // 分 updateClock(); // RTCに日時を設定 } // set ボタンが押される毎にdtStringの指定位置の値をインクリメント、液晶を更新 // ent ボタンが押されたら戻る void setV(int p, int minV, int maxV){ // p:ポインタ、minV:最小値、maxV:最大値 int xx; char upper; char lower; lcd.setCursor(p, 1); // 液晶のカーソルを移動 lcd.cursor(); // カーソル表示 xx = (dtString[p-1] & 0x0f) * 10 + (dtString[p] & 0x0f); //文字列から初期値を計算。BCDなので上位は10倍 while(digitalRead(entB)==LOW){ // entBがOFFになるまで待つ delay(30); } while(digitalRead(entB) == HIGH){ // entBがNOでなければ以下の処理行う if(digitalRead(selB) == LOW){ delay(30); // selBが押された! xx++; // xxをインクリメント if(xx >= maxV+1){ // 上限超えてたらゼロに戻す xx=minV; } upper = (xx / 10) | 0x30; // 上位をASCIIへ変換 lower = ( xx % 10) | 0x30; // 下位をASCIIへ変換 lcd.setCursor(p-1, 1); lcd.print(upper); // 表示更新 lcd.print(lower); lcd.setCursor(p, 1); // 液晶のカーソルを元の位置に戻す dtString[p-1]=upper; // データ文字列更新 dtString[p]=lower; while(digitalRead(selB)==LOW){ // selBが離されるまで待つ } delay(30); } } delay(30); lcd.noCursor(); // 液晶のカーソルを消す } void updateClock(){ // RTCに値をセット byte data[7]; int yy, mm, dd, ww; yy = (dtString[2] & 0xf) * 10 + (dtString[3] &0xf) + 2000; mm = (dtString[5] & 0xf) * 10 + (dtString[6] &0xf); dd = (dtString[8] & 0xf) * 10 + (dtString[9] &0xf); if( mm <= 2){ // ツェラーの式で曜日を求める mm+=12; yy--; } ww = (yy + yy/4 - yy/100 + yy/400 + (13*mm + 8)/5 + dd) % 7; for(int n = 0; n <= 15; n++){ // デバッグ用 Serial.print(dtString[n]); } Serial.print(" Week= "); Serial.println(ww); data[0] = 0x00; // 00秒にリセット data[1] = ((dtString[14] & 0xf) << 4) | (dtString[15] & 0x0f); // 分 data[2] = ((dtString[11] & 0xf) << 4) | (dtString[12] & 0x0f); // 時 data[3] = ((dtString[8] & 0xf) << 4) | (dtString[9] & 0x0f); // 日 data[4] = ww; // 曜日 data[5] = ((dtString[5] & 0xf) << 4) | (dtString[6] & 0x0f); // 月 data[6] = ((dtString[2] & 0xf) << 4) | (dtString[3] & 0x0f); // 年 Rtc.sync(data); } void tempOffsetChange(){ // 温度の補正値変更ルーチンコール lcd.clear(); lcd.print("Temp Offset Adj."); eepChange(0); } void pressOffsetChange(){ // 気圧の補正値変更ルーチンコール lcd.clear(); lcd.print("Press Offset Adj"); eepChange(2); } // EEPROM書き換えルーチン void eepChange(int x){ // x は書き換え対象アドレス int pol; int offset,LSB,MSB; byte eepVal; unsigned long t; while(digitalRead(entB)==LOW | digitalRead(selB)==LOW){ // ENTとSETの両方が押されていない状態まで待つ } delay(30); // チャッタ取り lcd.setCursor(0, 1); // lcd.print("EEPROM="); // 2行目の先頭文字 MSB=EEPROM.read(x); LSB=EEPROM.read(x+1); MSB=MSB << 8; offset= MSB | LSB; // 符号を保存するためビット操作で合成 lcd.print(offset); // 初期値表示 lcd.setCursor(6, 1); lcd.print("=> "); lcd.print(offset); // 初期値表示 for(;;){ // リセットされるまで繰り返す while(digitalRead(entB)==HIGH & digitalRead(selB)==HIGH){ // ENTとSETの両方が押されていなかったら待つ } delay(30); // チャッタ対策 t=millis(); // 押されたタイミングを記録 if( digitalRead(selB)==LOW){ pol = 1; // SETが押されたらプラス } else{ pol = -1; // ENTが押されたのでマイナス } offset=offset + pol; // オフセット値を修正 lcd.setCursor(9, 1); lcd.print(offset); // 現在の値を液晶に表示 while((digitalRead(entB)==LOW) || (digitalRead(selB)==LOW)){ // ボタンが離されるまで待つ if( millis()> t+500){ // 但し0.5秒以上押されていた場合は offset=offset+pol; lcd.setCursor(9, 1); lcd.print(offset); // 液晶に表示 lcd.print(" "); // 後ろのゴミ消し delay(10); // 10ms間隔(毎秒100回)で高速更新 } } eepVal= offset >> 8; EEPROM.write(x, eepVal); // 上位書き込み eepVal=offset & 0x00FF; EEPROM.write(x+1, eepVal); // 下位書き込み delay(30); // チャッタ対策 } // リセットされるまでループを繰り返す } // ルーチンの終わり // トレンドグラフ表示 void trendWrite(int trendN){ int n,v; long trendMax; long trendMin; trendMax = trendP[0]; // MaxとMinの暫定値として先頭のデータを採用 trendMin = trendP[0]; for(n=1; n < trendN; n++){ // 配列の中のMaxとMinを探す if(trendMax < trendP[n]){ trendMax = trendP[n]; } if(trendMin > trendP[n]){ trendMin = trendP[n]; } } lcd.clear(); if(trendMax == trendMin){ // 差が無かったらグラフは書かない lcd.print("No Data Change!"); lcd.setCursor(0, 1); dispPress(trendMin); } else{ // グラフ作成 dispPress(trendMax); lcd.setCursor(0, 1); dispPress(trendMin); for(n=trendN-1; n >= 0; n--){ // 古い値からグラフをプロット v = map(trendP[n], trendMin, trendMax, 1, 14); // 縦軸用に数値を1-14の値に変換 if(v >=8){ // 8以上なら lcd.setCursor(15-n, 0); // 上段を選んで lcd.write(v-7); // 外字で線を引く、vは外字番号 } else{ // 7以下なので、 lcd.setCursor(15-n, 1); // 下段に書く lcd.write(v); } } } } // 気圧を液晶に表示(引数は0.1ステップ) void dispPress(int x){ if(x < 10000) { lcd.print(" "); } lcd.print(x/10); lcd.print("."); lcd.print(x % 10); } // 液晶の外字設定 void defGaiji(){ byte x[8] = { // 外字で℃を作る B00011, B00011, B01100, B10010, B10000, B10010, B01100, }; lcd.createChar(0, x); byte bar1[8] = { // 同じ変数を使いまわのは面倒なので・・ B00000, B00000, B00000, B00000, B00000, B00000, B00111, }; lcd.createChar(1, bar1); byte bar2[8] = { // B00000, B00000, B00000, B00000, B00000, B00111, B00000, }; lcd.createChar(2, bar2); byte bar3[8] = { // B00000, B00000, B00000, B00000, B00111, B00000, B00000, }; lcd.createChar(3, bar3); byte bar4[8] = { // B00000, B00000, B00000, B00111, B00000, B00000, B00000, }; lcd.createChar(4, bar4); byte bar5[8] = { // B00000, B00000, B00111, B00000, B00000, B00000, B00000, }; lcd.createChar(5, bar5); byte bar6[8] = { // B00000, B00111, B00000, B00000, B00000, B00000, B00000, }; lcd.createChar(6, bar6); byte bar7[8] = { // B00111, B00000, B00000, B00000, B00000, B00000, B00000, }; lcd.createChar(7, bar7); }