/* 水草水槽のCO2発生量コントロール V2.0 圧力計追加バージョン 2018/11/13 ラジオペンチ */ #include // 時間はタイマー割り込みで刻む #include // I2Cキャラクタ液晶を使用 #include #include #define decPin 9 // -ボタン #define incPin 10 // + ボタン #define entPin 11 // enter ボタン volatile int secCount = 0; int pumpOnTime; // ポンプON時間 (単位:ms)(最小滴下量以上に設定すること) int pumpIntervalDay; // 昼間のインターバル(単位:秒) int pumpIntervalNight; // 夜間のインターバル(単位:秒)、圧力低下防止のために必要 int pumpInterval; // ポンプ駆動周期 int lastDayNightFlag; // 前回の昼夜フラグ int dayNightFlag; // 現在の昼夜フラグ int ss = 10; // ビルドアップショット数 int ssInterval = 1500; // ビルドアップショット間隔(単位:ms) float k1 = 931.1; // 1V当たりの圧力(mmAqの値)、要調整 float zeroPressV = 0.5; // 気圧ゼロの時の電圧 int mmAq; // mmAq単位の気圧(表現出来る範囲は±3.2kg/cm2) char buff[20]; // 文字列操作バッファ(sprintfで使用) volatile int timeUpFlag = 0; // タイムアップフラグ I2CLiquidCrystal lcd(30, (bool)true); // 電源電圧5V void setup() { Serial.begin(115200); lcd.begin(8, 2); // 8文字2行液晶 pinMode(6, OUTPUT); // ポンプ駆動信号 pinMode(7, OUTPUT); // 動作確認表示 pinMode(8, INPUT_PULLUP); // 昼夜信号(昼はLOW,夜はHIGH) pinMode(decPin, INPUT_PULLUP); pinMode(incPin, INPUT_PULLUP); pinMode(entPin, INPUT_PULLUP); pinMode(13, OUTPUT); lcd.clear(); lcd.print("CO2doser"); lcd.setCursor(0, 1); lcd.print(" v2.0"); readEEPROM(); // EEPROMからパラメーター読んで値を設定(変な値はデフォルトに修正) delay(1000); MsTimer2::set(1000, timeIntrp); // 1秒毎に timeIntrpをコール MsTimer2::start(); Serial.println("Auto CO2 injecter start"); shotCo2(); // 開始時に1ショット(つまりリセット毎に1ショット) setDayNight(); // 昼夜インターバルセット lastDayNightFlag = dayNightFlag; // フラグを記録 } void loop() { while (timeUpFlag == 0) { // 1秒間待つ間に下記を実行 if (digitalRead(incPin) == LOW) { // + ボタンが押されていたら shotCo2(); // 1ショット secCount = 0; delay(1000); } if (digitalRead(entPin) == LOW) { // entボタンが押されていたらパラメータ変更メニューを実行 paraSet(); delay(30); } } // 1秒経過待ち中の処理end digitalWrite(7, HIGH); // 動作確認表示ON timeUpFlag = 0; // タイムアップフラグをクリア setDayNight(); // 昼夜情報を読んで必要なパラメーターを設定 if ((lastDayNightFlag == 1) && (dayNightFlag == 0)) { // 前回が夜で今回は昼だったら(つまり朝になった直後なら) Serial.println("Morning build up done"); buildUp(); // 内圧上昇させるために強制注入(ビルドアップ) } lastDayNightFlag = dayNightFlag; // 次回のためにフラグを記録 secCount++; dispLCD(); Serial.print(secCount); Serial.print(" / "); Serial.print(pumpInterval); if (secCount >= pumpInterval) { // 指定時間だったら secCount = 0; // 秒カウンタをリセット Serial.print(", Dose executed"); shotCo2(); // ポンプ駆動 } Serial.println(); delay(20); // LED点灯確認のために待つ digitalWrite(7, LOW); // 動作確認表示OFF } void readEEPROM() { // EEPROMからパラメーターを読む(異常値はデフォルトに置き換え) int k; k = EEPROM.read(0) << 8 | EEPROM.read(1); // ポンプ運転時間 if ((k < 5) || ( 100 < k)) { // 5〜100の範囲外だったら k = 30; // 30msに設定 } Serial.println(k); pumpOnTime = k; k = EEPROM.read(2) << 8 | EEPROM.read(3); // 昼間のポンプ運転間隔 if ((k < 10) || ( 990 < k)) { // 10〜990の範囲外だったら k = 240; // 240秒に設定 } Serial.println(k); pumpIntervalDay = k; k = EEPROM.read(4) << 8 | EEPROM.read(5); // 夜間のポンプ運転間隔 if ((k < 100) || ( 9900 < k)) { // 100〜9900の範囲外だったら k = 2400; // 2400秒に設定 } Serial.println(k); pumpIntervalNight = k; k = EEPROM.read(6) << 8 | EEPROM.read(7); // モーニングショット数 Serial.print(k); Serial.print(" , "); if ((k < 0) || ( 30 < k)) { // 0〜30の範囲外だったら k = 10; // 10ショットに設定 } Serial.println(k); ss = k; k = EEPROM.read(8) << 8 | EEPROM.read(9); // モーニングショット間隔 if ((k < 200) || ( 2000 < k)) { // 200〜2000の範囲外だったら k = 1500; // 1500msに設定 } Serial.println(k); ssInterval = k; } void writeEEPROM() { // EEPROMに設定値を保存 EEPROM.write(0, (pumpOnTime >> 8)); EEPROM.write(1, (pumpOnTime & 0xFF)); EEPROM.write(2, (pumpIntervalDay >> 8)); EEPROM.write(3, (pumpIntervalDay & 0xFF)); EEPROM.write(4, (pumpIntervalNight >> 8)); EEPROM.write(5, (pumpIntervalNight & 0xFF)); EEPROM.write(6, (ss >> 8)); EEPROM.write(7, (ss & 0xFF)); EEPROM.write(8, (ssInterval >> 8)); EEPROM.write(9, (ssInterval & 0xFF)); } void buildUp() { // ビルドアップショットを実行 lcd.clear(); lcd.print("Start Up"); for (int n = 0; n < ss; n++) { // 指定回数 shotCo2(); // ポンプ駆動 lcd.setCursor(3, 1); lcd.print(n + 1); lcd.print("/"); lcd.print(ss); lcd.print(" "); delay(ssInterval); // 指定時間待つ } lcd.clear(); } void dispLCD() { lcd.clear(); lcd.print("Run"); // 1行目先頭から sprintf(buff, "%4d", pumpInterval - secCount); // 残り時間を4桁右詰め文字列に変換 lcd.setCursor(3, 0); lcd.print(buff); lcd.print("s"); if (digitalRead(decPin) == LOW) { // decボタンが押されていたら ショット時間、周期表示 lcd.setCursor(0, 1); lcd.print(" m"); // 2行目 sprintf(buff, "%2d", pumpOnTime); lcd.setCursor(0, 1); lcd.print(buff); // ショット時間表示 sprintf(buff, "%4d", pumpInterval); // 値を4桁右詰め文字列に変換 lcd.setCursor(3, 1); lcd.print(buff); lcd.print("s"); } else { // decボタンが押されてなければ、圧力表示 mmAq = readPress(); lcd.setCursor(0, 1); lcd.print("p="); // 先頭文字表示 lcd.setCursor(2, 1); // 2行目3文字目から、 sprintf(buff, "%4d", mmAq); // 右詰め4文字で (マイナスは-nnn) lcd.print(buff); lcd.print("mm"); // 圧力を表示(mmAq単位) Serial.println(buff); } } int readPress() { // 圧力を測定してmmAq単位の値で返す float p; // 圧力計算結果 long d; // ADC結果バッファ long sumd = 0; // ADC累積値 for (int n = 0; n < 892; n++) { // 892回で0.1秒(実測=100.06ms)、50Hz,60Hzで割り切れるので電源ノイズ低減が期待できる d = analogRead(0); // A0の値を読む sumd = sumd + d; // 平均処理のために累積 } // 圧力計算式 p = k1 * ((sumd * 5.0) / (892.0 * 1024.0) - zeroPressV ) + 0.5; return (int)p; // 結果をintで返す } void shotCo2() { lcd.clear(); lcd.print("Inject"); sprintf(buff, "%3d", pumpOnTime); lcd.setCursor(3, 1); lcd.print(buff); // 2行目先頭にポンプ時間表示 lcd.print("ms"); digitalWrite(6, HIGH); // ポンプON digitalWrite(13, HIGH); delay(pumpOnTime); // 指定所間ポンプをON digitalWrite(6, LOW); // ポンプOFF digitalWrite(13, LOW); } void setDayNight() { if (digitalRead(8) == LOW) { pumpInterval = pumpIntervalDay; // 昼間 dayNightFlag = 0; // フラグは0 } else { pumpInterval = pumpIntervalNight; // 夜間 dayNightFlag = 1; // フラグは1 } } void paraSet() { // 運転パラメーター設定 lcd.clear(); lcd.print("ParamSet"); while (digitalRead(entPin) == LOW) { // Entボタンが離されるまで待つ } setShotTime(); // ポンプの動作時間設定 setDayInterval(); // 昼間のインターバル setNightInterval(); // 夜間のインターバル setSShotN(); // モーニングスタートパルス数 setSShotI(); // モーニングスターとパルスの間隔 writeEEPROM(); // 設定値をEEPROMに書き込み } void setShotTime() { // ポンプ駆動時間設定 lcd.clear(); lcd.print("Shot Tw"); lcd.setCursor(6, 1); lcd.print("ms"); pumpOnTime = LcdRW(pumpOnTime, 5, 5, 95); // step=5ms,min=5ms, max=95ms(2桁を超えると表示が壊れる) } void setDayInterval() { // 昼間のポンプ運転間隔設定 lcd.clear(); lcd.print("DayInt. "); lcd.setCursor(6, 1); lcd.print("s"); pumpIntervalDay = LcdRW(pumpIntervalDay, 10, 10, 990); // step=10s,min=10s, max=999s } void setNightInterval() { // 夜間のポンプ運転間隔設定 lcd.clear(); lcd.print("NightInt"); lcd.setCursor(6, 1); lcd.print("s"); pumpIntervalNight = LcdRW(pumpIntervalNight, 100, 100, 9900); // step=100s,min=100s, max=9900s } void setSShotN() { // モーニングスタートアップショット数設定 lcd.clear(); lcd.print("SS N"); lcd.setCursor(6, 1); lcd.print(" "); ss = LcdRW(ss, 1, 0, 30); // 下限は0でこの場合ショットは無し } void setSShotI() { // モーニングスタートアップショットの時間設定 lcd.clear(); lcd.print("SS Int"); lcd.setCursor(6, 1); lcd.print("ms"); ssInterval = LcdRW(ssInterval, 100, 200, 2000); } // 液晶に4桁右詰めで値を表示し、ボタン操作で値を変更。変更した値を // 戻り値として返す。表示位置の先頭は2行目/3文字目で固定。 // 引数:変更したい値、変更ステップ量、下限値、上限値 // int LcdRW(int x, int stepX, int minX, int maxX) { // 指定位置に値を表示して数値を修正 while (digitalRead(entPin) == HIGH) { // enterボタンが押されていない間は lcd.setCursor(2, 1); // 2行目の3文字目から、 sprintf(buff, "%4d", x); lcd.print(buff); // 4桁右寄せで値を表示 if (digitalRead(incPin) == 0) { // + ボタンが押されていたら x = x + stepX; // x を指定ステップ増加 Serial.println(x); if (x > maxX) { x = maxX; } delay(30); while (digitalRead(incPin) == 0) { // ボタンが離されるまで待つ } delay(30); } if (digitalRead(decPin) == 0) { // - ボタンが押されていたら x = x - stepX; // x を指定ステップ減らす Serial.println(x); if (x < minX) { x = minX; } delay(30); while (digitalRead(decPin) == 0) { // ボタンが離されるまで待つ } delay(30); } } delay(30); while (digitalRead(entPin) == LOW) { // ent ボタンが離されるまで待つ } delay(30); return x; // 戻り値を設定 } void timeIntrp() { // MsTimer2 割込み処理 timeUpFlag = 1; // 1秒経過フラグON }