/* 電力ロガー 誘導型電力量計の円盤の回転を検出して電力表示 回転検出と測定間隔決定に割り込み使用 記録条件はSDカード上の LogConf.txtで指定。下記は設定例 SD logger test 2017 / 09 / 29 Log00700.csv 3 N, sec, power(W), energy(Wh), brightness, cpuTemp(C), cpuVcc(V) 2018/10/11 ラジオペンチ http: //radiopench.blog96.fc2.com/ */ #include #include #include // http://n.mtng.org/ele/arduino/i2c.html #include #include #define holeSigPin 2 const int chipSelect = 4; // SDカードのChip Selectピン const int logSW = 5; // ログ記録スイッチ const int ledPin = 6; // LEDピン unsigned long IC = 0; // インターバルカウンタ unsigned long LC = 0; // 行カウンタ(LineCounter) unsigned long TC = 0; // 秒カウンタ(TimeCounter) unsigned long logInterval; volatile int sampleFlag = 0; String readBuff; // ファイルのラインリードバッファ String logTitle; // ログのタイトル情報 String logFileName; // ログファイル名 String colName; // データー名(先頭行に出力される) String logData; volatile unsigned long tLast; // 前回のパルス時刻(ms) volatile unsigned long tDiff; // パルス間隔 volatile unsigned long tNow; // 現在時刻 volatile unsigned long holeCount = 0; // 穴数カウンタ volatile unsigned long NN = 0; // 累積時間カウンタ volatile int timeUpFlag = 0; // タイムアップフラグ float Watt = 0.0; // 電力 float Wh = 0.0; // 積算電力量 const float SFw = 1500.0; // (ScalFactor) 電力換算係数 SFw / パルス間隔(s) = W  // 600rev/kWhで穴2個×2相合成で2400パルス/kWh → 1W = 1000*3600/2400=1500 const float SFe = 2.4; // 電力量換算係数 パルス数 * SFe = Wh float V[4]; // 電圧測定結果 (ch0〜ch3) float Temp, Vcc; // CPU温度、Vcc測定結果 File myFile; // SD読み書き用 I2CLiquidCrystal lcd(30, true); // コントラスト(0-63),液晶電源(true=5V, false=3.3V) void setup() { pinMode(holeSigPin, INPUT_PULLUP); // クリーピング防止穴割り込み入力ピン pinMode(logSW, INPUT_PULLUP); // 記録スイッチ pinMode(ledPin, OUTPUT); // 動作表示LED pinMode(chipSelect, OUTPUT); // SDのchip selectピン pinMode(A3, INPUT_PULLUP); // フォトトラのコレクタ抵抗代わりにプルアップ Serial.begin(115200); lcd.begin(16, 2); lcd.print("Starting..."); delay(1000); Serial.print("Reading SD card..."); if (!SD.begin(chipSelect)) { Serial.println("SD failed!"); errorStop(); // エラー表示して停止 } digitalWrite(ledPin, HIGH); Serial.println("initialization done."); Serial.println(); readConfig(); // ログ条件設定ファイル読み出し Serial.println("Log settings (LogConf.txt)"); Serial.print("Data title = "); Serial.println(logTitle); Serial.print("File name = "); Serial.println(logFileName); Serial.print("Rec.interval = "); Serial.print(logInterval); Serial.println("sec"); Serial.print("Data lavel = "); Serial.println(colName); lcdDispConf(); // ログ条件を液晶に表示 digitalWrite(ledPin, LOW); delay(1000); } void loop() { digitalWrite(ledPin, LOW); while (digitalRead(logSW) == HIGH) { // 記録スイッチがONになるまで待つ } myFile = SD.open(logFileName, FILE_WRITE); // ログファイルを開く if (myFile == 0) { // エラー処理 Serial.println("Cant open log file."); errorStop(); // LED点滅 } Serial.println(); myFile.println(); // ログパラメーターを出力 Serial.println(logTitle); myFile.println(logTitle); // タイトル Serial.println(colName); myFile.println(colName); // データーラベル myFile.close(); LC = 0; // 行数カウンタ初期化 TC = 0; // タイムカウンタ初期化 IC = 0; // インターバルカウンタ初期化 holeCount = 0; MsTimer2::set(1000, sensInterval); // 1000ms間隔でMsTimer2割込み MsTimer2::start(); attachInterrupt(0, holeDetect, FALLING); // pin2(int0)のネガエッジで割込み while (digitalRead(logSW) == LOW) { // 記録スイッチがONの間は以下を繰り返し実行 digitalWrite(ledPin, HIGH); while (timeUpFlag == 0) { // MsTimer2の割込みが入るまで待つ } timeUpFlag = 0; digitalWrite(ledPin, LOW); // 処理中表示のためLEDを消灯 sensData(); // データー取得して記録文字列に変換 lcdDisp1(); // 液晶の1行目表示(記録行数、温度など) lcdDisp2(); // 液晶の2行目表示(データ内容) if (IC == 0) { // インターバルカウンタがゼロだったらデータ書き込み LC++; // 行カウンタをインクリメント File myFile = SD.open(logFileName, FILE_WRITE); // ログファイルを開く if (myFile == 0) { Serial.println("Cant open log file."); errorStop(); } Serial.println(logData); myFile.println(logData); // ファイルにログデーター書き込み myFile.close(); delay(200); // LEDを長めに消灯させるためにちょっと待つ } TC++; // 累積秒数カウンタインクリメント IC++; if (IC == logInterval) { // インターバルカウンタが設定値になっていたら IC = 0; // 次回のためにリセット } digitalWrite(ledPin, HIGH); // 処理が終わったのでLED点灯 } // 記録スイッチONの時のループの末尾 lcdDispConf(); // 液晶をログ条件表示に変更 MsTimer2::stop(); // 記録スイッチがOFFになったので割込みタイマー停止 } void readConfig() { // ログ条件ファイルを読む myFile = SD.open("LogConf.txt", FILE_READ); // 設定ファイルを開く if (myFile) { lineRead(); // 1行目 logTitle = readBuff; lineRead(); // 2行目 logFileName = readBuff; lineRead(); // 3行目 logInterval = readBuff.toInt(); // ログ取得周期 値は整数に変換 lineRead(); // 4行目 colName = readBuff; myFile.close(); } } void lineRead() { // ファイルから1行読んで readBuffに格納する // 行末コード win:0x0D,0x0A unix:0x0A mac:0x0D char s; int x = 0; readBuff = ""; // String型 for (;;) { s = myFile.read(); // ファイルから1文字読む if (s == 0x0A) { // LF(0x0A)なら行末とみなして break; // ループを抜ける } if (s != 0x0D) { // CR(0x0D)以外だったら、つまり0x0Dは無視 readBuff += s; // 文字追加。Stringなので末尾には0x00が自動挿入される x++; } } } void sensData() { // 電力測定と記録用文字列の作成 int x; float voltage; logData = ""; logData += String(LC, DEC); // 1列目は行番号 logData += ", "; logData += String(TC, DEC); // 2列目は経過時刻(秒) logData += ", "; logData += String(holeCount); logData += ", "; Watt = 0.0; Wh = 0.0; if (holeCount >= 2) { Watt = SFw / (tDiff / 1000.0); // 電力計算 Wh = (holeCount - 1) / SFe; // 累積電力量計算 } logData += String(Watt, 2); logData += ", "; // 3列目は電力 logData += String(Wh, 2); logData += ", "; // 4列目は電力量 x = analogRead(3); voltage = 5.0 * x / 1023.0; // 明るさセンサーの値を読む logData += String(voltage, 2); // 5列目、文字列に変換して追加(小数点以下2桁まで) logData += ", "; logData += String(Temp, 1); // 6列目、温度(値は1行目の表示で測定済) logData += ", "; logData += String(Vcc, 2); // 7列目、電圧 } void lcdDispConf() { lcd.clear(); lcd.print(logTitle.substring(0, 16)); lcd.setCursor(0, 1); lcd.print(logFileName); lcd.print(" "); lcd.print(logInterval % 1000); // 下3桁だけ表示 } void lcdDisp1() { // 1行目の表示(毎秒更新) unsigned long x; x = (logInterval - IC) % 1000; // ログインターバルの残り時間(表示のmaxは999) lcd.setCursor(0, 0); if (x < 100) { // ゼロサプレス処理 lcd.print(" "); } if (x < 10) { lcd.print(" "); } lcd.print(x); lcd.print(" "); // インターバル残り時間表示 x = LC % 100000UL; // 記録行数表示(表示のmaxは99999) if (x < 10000) { // ゼロサプレス処理 lcd.print(" "); } if (x < 1000) { lcd.print(" "); } if (x < 100) { lcd.print(" "); } if (x < 10) { lcd.print(" "); } lcd.print(x); lcd.print("L"); // 記録行数表示 lcd.print(" "); Temp = cpuTemp(); // CPU温度測定 Vcc = cpuVcc(); // CPU電源電圧測定 lcd.print(Temp, 1); lcd.print("c "); // 温度表示する場合 // lcd.print(Vcc, 2); lcd.print("V "); // 電圧表示する場合 } void lcdDisp2() { lcd.setCursor(0, 1); // 2行目の先頭から、 lcd.print(Watt, 1); lcd.print("W "); lcd.setCursor(7, 1); lcd.print(Wh, 1); lcd.print("Wh "); } // CPU温度・電圧測定(流用する場合は以下をコピーする) float cpuTemp() { // CPU温度測定関数 long sum = 0; adcSetup(0xC8); // Vref=1.1V, input=ch8 for (int n = 0; n < 100; n++) { sum = sum + adc(); // adcの値を読んで積分 } return (sum * 1.1 / 102.4) - 347.5; // 温度を計算して戻り値にする。-347.5は要調整 } float cpuVcc() { // 電源電圧(AVCC)測定関数 long sum = 0; adcSetup(0x4E); // Vref=AVcc, input=internal1.1V for (int n = 0; n < 10; n++) { sum = sum + adc(); // adcの値を読んで積分 } return (1.1 * 10240.0) / sum; // 電圧を計算して戻り値にする } void adcSetup(byte data) { // ADコンバーターの設定 ADMUX = data; // ADC Multiplexer Select Reg. ADCSRA |= ( 1 << ADEN); // ADC イネーブル ADCSRA |= 0x07; // AD変換クロック CK/128 delay(10); // 安定するまで待つ } unsigned int adc() { // ADCの値を読む unsigned int dL, dH; ADCSRA |= ( 1 << ADSC); // AD変換開始 while (ADCSRA & ( 1 << ADSC) ) { // 変換完了待ち } dL = ADCL; // LSB側読み出し dH = ADCH; // MSB側 return dL | (dH << 8); // 10ビットに合成した値を返す } // CPU温度・電圧測定 ------ ここまでコピー ------------- void errorStop() { // エラー表示して停止 lcd.clear(); lcd.print("Error"); for (;;) { // 無限ループでLED点滅 digitalWrite(ledPin, HIGH); delay(20); digitalWrite(ledPin, LOW); delay(150); } } void sensInterval() { // Mstimet2の割り込み処理(タイマー割り込み) timeUpFlag = 1; // タイムアップグフラグを立てる NN++; // 累積時間インクリメント } void holeDetect() { // クリーピング防止ホール割り込み処理(int.0ピン割り込み) tNow = millis(); // 現在のシステム時刻取得 if ((tNow - tLast) > 30) { // 前回パルスから30ms以上経過している場合だけ計算(気休め) tDiff = tNow - tLast; // 時間差計算 (50日でオーバーフローするので注意) tLast = tNow; // 前回値として記録 holeCount++; // 穴数カウントアップ } }