/* グリッドタイインバーター ラジオペンチ http://radiopench.blog96.fc2.com/ MPPTを0.1us単位に変更 11/23 MPPT判定データ修正。 その他不要な部分を削除 いけそうなので11/29から走らせてみる。 FreqError時に発電量をEEPROMに保存 1/5 バックライトを夜間のみ点灯 1/12 xivelyへのアップロード対応 6/10 コンパイル後サイズ:12506バイト */ #include #include #include #define TransONpin 6 // メイントランスリレー #define DispModePin 7 // 表示モード切替ピン #define ACsyncPin 8 // ACライン同期信号入力ピン番号=2 #define PgatePin 9 // ポジ側FETドライブピン #define NgatePin 10 // ネガ側FETドライブピン #define VoltagePin 0 // パネル電圧測定ピン #define CurrentPin 1 // パネル電流測定ピン #define BackLightPin 19 // バックライト出力ピン #define SumChargeAdr 0 // EEPROMの累積充電量記録先頭アドレス #define TodayChargeAdr 4 // EEPROMの本日充電量記録先頭アドレス #define VC 117UL // 電圧換算係数 #define IC 1010UL // 電流換算係数 // #define Ioff 11 // 電流オフセット補正係数(旧回路では132) #define V1 30 // これ以下なら夜 #define V2 70 // これ以上なら昼 #define V3 100 // これ以下なら送電停止(送電中の電圧) #define V4 185 // これ以上なら送電開始 LiquidCrystal lcd(12, 11, 5, 4, 3, 2); unsigned long powTime; float ACfreq; long PWu = 600; // パルス幅(us) long t1,t2; // パルス出力タイミング(us) int Ioff = 0; // 電流検出オフセット値 int mppt_c = 0; // MPPT インターバルカウンタ long lastPow = 0; int mppt_vector = 1; long mpptPowLast=0; // 前回のパワー long mpptPowNow; // 今回のパワー boolean vectorChangeFlag; int dispInterval, SumInterval; // 表示インターバルカウンタ long panelVoltage, panelCurrent, panelPow; unsigned long MicrosLast, MicrosNow, ACinterval; unsigned long SumPowToday; int dispModeN = 0; long temp; int adcV, adcI; unsigned long SumCharge,TodayCharge; // 累積充電量(単位は0.01Wh) unsigned long powTemp=0; // 電力積算計算用バッファ unsigned long arago=0; // アラゴの円盤表示ポインタ boolean freqFlag = false; boolean modeSWlast, modeSWnow; boolean RunACflag = false; // AC変換実行フラグ boolean lastRunACflag; boolean DayTime; // 昼夜判定フラグ boolean lastDayTime; boolean dayNightChanged = false; // 昼夜が変わった直後だけtrue boolean eepSaveFlag = false; void setup() { lcd.begin(16,2); analogReference(INTERNAL); // ADCのフルスケール=1.1Vに設定 Serial.begin(9600); Serial.println(""); // 改行 Serial.println("GridTieInverter starting V1.08"); // バージョン表示 pinMode(DispModePin, INPUT); // 表示モードピン digitalWrite(DispModePin, HIGH); // プルアップ pinMode(TransONpin, OUTPUT); pinMode(ACsyncPin, INPUT); pinMode(PgatePin, OUTPUT); digitalWrite(PgatePin, LOW); // こうやってもリセット中はHighになるので対策が必要 pinMode(NgatePin, OUTPUT); digitalWrite(NgatePin,LOW); pinMode(BackLightPin, OUTPUT); // 液晶バックライト制御ピン digitalWrite(BackLightPin, HIGH); // バックライト点灯 defGaiji(); // 液晶の外字を定義 modeSWlast = digitalRead(DispModePin); // スイッチ押し検出用フラグ // eepWL(0,422000UL); // EEPROMへ累積発電量書き込み 単位は0.01Wh(修正用) // eepWL(4,9500); // EEPROM 本日の発電量初期化 単位は0.01Wh /* for(;;){ Serial.println(analogRead(CurrentPin)); // 電流センス調整用 delay(500); } */ /* eepWL(SumChargeAdr,0); eepWL(TodayChargeAdr,0); Serial.println(eepRL(SumChargeAdr)); Serial.println(eepRL(TodayChargeAdr)); */ SumCharge = eepRL(SumChargeAdr); // EEPROMから累積充電量を読み出す TodayCharge = eepRL(TodayChargeAdr); // EEPROMから累積充電量を読み出す // Serial.println(SumCharge); // Serial.println(SumPowToday); // saveTodayCharge(); /* for(int x =0; x<=1023; x++){ // EEPROM erase EEPROM.write(x, 0); } */ lcd.clear(); lcd.print("GridTieInverter"); // オープニングメッセージ lcd.setCursor(0,1); lcd.print("V1.08 starting.."); // 2行目にバージョン表示 eepDump(); // 発電量の記録を垂れ流す delay(3000); // 立ち上がりを待つ lcd.clear(); for(int n=1; n<=10; n++){ // 10回読んで Ioff = Ioff + analogRead(CurrentPin); } Ioff = Ioff / 10; // 平均値を電流測定のオフセット値にする lcd.print("Ioff= "); lcd.print(Ioff); Serial.print("Ioff ="); Serial.println(Ioff); delay(2000); lcd.clear(); panelVoltage = VoltMeasure(); // パネルの電圧を測定 if(panelVoltage < V1){ // 開始時の昼夜判定 DayTime = false; // V1 以下なら夜 lastDayTime = false; } else{ DayTime = true; // V1以上ならとりあえず昼 lastDayTime = true; // (本来はV2以上で昼) } } void loop(){ panelVoltage = VoltMeasure(); // 電圧により動作モードを設定 if(panelVoltage < V1){ // もし夜だったら waitMorning(); // 夜明けを待つ } if(panelVoltage > V4){ // 送電開始電圧以上? RunACflag = true; } if(panelVoltage < V3){ // 送電停止電圧以下? RunACflag = false; } if((RunACflag == true) & (lastRunACflag == false)){ // 変換開始の最初だけ TransON(); // トランスをON } if(RunACflag == true){ RunAC(); // ACに同期してFETをON/OFFさせ送電 mppt(); // MPPTの処理 // mpv(); // 電圧一定制御 } else{ TransOFF(); // トランスをOFF } lastRunACflag = RunACflag; } // Loop void waitMorning(){ // 夜に入った後の処理 digitalWrite(BackLightPin, HIGH); // バックライト点灯 Serial.println("Sleeping"); // シリアルにメッセージ流す lcd.clear(); lcd.setCursor(0,0); lcd.print("SLEEPING "); // dispNNRN(panelVoltage); lcd.print("V"); lcd.setCursor(0,1); dispNNNRNN(TodayCharge); lcd.print("/"); dispNNNNNRN((SumCharge+5)/10); lcd.print("Wh"); // eepWL(SumChargeAdr, SumCharge); // 累積発電量をEEPROMに保存 eepWL(TodayChargeAdr, TodayCharge); // リセットされた場合に備えて本日分も保存 while( VoltMeasure() < V2){ // 朝(V2以上)になるまでここで待つ panelVoltage = VoltMeasure(); lcd.setCursor(11,0); dispNNRN(panelVoltage); lcd.print("V"); Serial.print(", 0, "); // 先頭のブランク, MPPT Serial.print(panelVoltage); // パネル電圧 Serial.println(", 0, 0, 0"); // , 電流, パワー, 詳細パワー delay(2000); // 2秒間待つ(夜は長めにしておく) } lcd.clear(); lcd.print("Good morning!"); lcd.setCursor(0,1); lcd.print("New Day started."); Serial.println("New Day started"); eepDump(); // 発電量の記録を垂れ流す delay(5000); digitalWrite(BackLightPin, LOW); // バックライト消灯 saveTodayCharge(); TodayCharge=0; eepWL(TodayChargeAdr,0); // EEPROMデーター消去 dispModeN = 0; // 表示モードをデフォルトに設定 } void RunAC() { digitalWrite(BackLightPin, LOW); // バックライト消灯 while(digitalRead(ACsyncPin) == LOW) { // Highになるまで何もせずに待つ } if( trigCheck() == 1){ // トリガ周期が正常か確認 freqError(ACinterval); // 周波数異常処理 } t1 = (10000-PWu)/2; // FETの制御タイミングを計算 t2 = t1 * 2; t1 = t1 - 250; // タイミング補正 Timer1.initialize(t1); // Timer1.attachInterrupt( TT1 ); // Timer1でタイミング割り込み開始 // 表示モード番号セット modeSWnow = digitalRead(DispModePin); // 現在のスイッチを読んで if((modeSWnow == LOW) & (modeSWlast == HIGH)){ // 今回押されていたら dispModeN++; // モード番号インクリメント if(dispModeN == 6){ dispModeN = 0; // eepRefresh(); // EEPROMに現在値を記録 eepSaveFlag=true; } dispInterval = 47; // 2サイクル後に表示されるよう設定 } modeSWlast = modeSWnow; temp = analogRead(CurrentPin) - Ioff; // Ioffはオペアンプの強制オフセット分 if(temp < 0){ // 負になったらゼロ temp =0; } adcI=temp; // adcIはmpptで使用 panelCurrent = ((temp * IC)+256UL) >> 9; // 換算。単位は0.001A panelPow = panelVoltage * panelCurrent; // パネル表示電力 単位は0.1mW // 液晶表示 lcdDisp(); // 液晶表示 // 累積処理 SumInterval++; if(SumInterval == 50){ // 50回(1秒)毎に powTemp = powTemp + (panelVoltage * panelCurrent); arago = arago + (panelVoltage * panelCurrent); SumCharge = SumCharge + powTemp / 360000UL; // 累積値を更新(0.01Wh単位で積分) TodayCharge=TodayCharge + powTemp / 360000UL; // 本日値を更新(0.01Wh単位で積分) powTemp = powTemp % 360000UL; // 余りは次回へまわす arago = arago % 600000UL; // アラゴの表示用 SumInterval = 0; } // 次のサイクル同期の準備 if(digitalRead(ACsyncPin) == HIGH){ // まだHIGHだったら while(digitalRead(ACsyncPin) == HIGH) { // LOWになるまで待つ } } } // RunACの末尾 void mppt(){ mppt_c++; if(mppt_c > 25){ // 25サイクル目から // if(mppt_c > 12){ // 12サイクル目から mpptPowNow = mpptPowNow + ((long)adcV * adcI); // パワーを累積 } if(mppt_c == 50){ // 50サイクル毎にMPPTで最適パルス幅を決定 // if(mppt_c == 25){ // 25サイクル毎にMPPTで最適パルス幅を決定 TypeMpptStatus(); if(mppt_vector == 1){ // 前回は増加方向で、 if(mpptPowNow > mpptPowLast){ // パワーが前回より増えていたら PWu = PWu + 100; // パルス幅を増加 vectorChangeFlag = false; } else{ // 減少か変わらなかったら PWu = PWu - 100; // パルス幅を減らし、 vectorChangeFlag = true; // ベクター変更フラグを立てる } } if(mppt_vector == -1){ // 前回は減少させて if(mpptPowNow > mpptPowLast){ // パワーが増えていたら PWu = PWu - 100; // パルス幅を更に減らす vectorChangeFlag = false; } else{ // 減少か変わらなかったら PWu = PWu + 100; // パルス幅を増やす vectorChangeFlag = true; } } if(vectorChangeFlag == true){ mppt_vector = mppt_vector * -1; // ベクターを反転 } if( PWu > 8000){ // 上下限の設定 PWu = 8000; } if( PWu < 600){ PWu = 600; } mpptPowLast = mpptPowNow; // 前回の値として記録 mpptPowNow =0; mppt_c = 0; } // if } // mppt() void TypeMpptStatus(){ // mpptの状態をシリアルに流す Serial.print(", "); // TeraTermのタイムスタンプから分離 Serial.print(PWu/100); Serial.print(", "); Serial.print(panelVoltage); Serial.print(", "); Serial.print(panelCurrent); Serial.print(", "); Serial.print(panelPow); Serial.print(", "); Serial.println(mpptPowNow); } void TransON(){ digitalWrite(TransONpin, HIGH); // トランスをON PWu = 600; // 再開時のためにmppt条件を初期化 mppt_c = 0; lastPow = 0; // MPP開始用に初期値はゼロ、 mppt_vector = 1; // フラグは増加方向で開始 delay(1000); // トランスが安定するまで待つ while(digitalRead(ACsyncPin) == HIGH) { // 位相がLOWになるまで待つ } } void TransOFF(){ // トランスをOFF unsigned long x; digitalWrite(TransONpin, LOW); // トランスOFF lcd.setCursor(0,0); lcd.print("STANDBY "); // dispNNRN(panelVoltage); lcd.print("V"); lcd.setCursor(0,1); dispNNNRNN(TodayCharge); lcd.print("/"); dispNNNNNRN(SumCharge/10); lcd.print("Wh"); // dispModeN = 0; // RUNの表示をデフォルトに戻す Serial.print(", 0, "); // 先頭のブランク, MPPT Serial.print(panelVoltage); // パネル電圧 Serial.println(", 0, 0, 0"); // , 電流, パワー, 詳細パワー delay(1000); // トランスが安定するまで待つ } void SolorDisp(){ // ソーラー入力状況表示 unsigned int x; lcd.setCursor(0,0); lcd.print("SOLAR IN"); dispNN(PWu/100); lcd.print(" "); x = panelVoltage; // 電圧 dispNNRN(x); // 00.0形式で表示 lcd.print("V"); lcd.setCursor(0,1); x = (panelPow + 50) / 100; // 電力を10mW単位にスケーリング // Serial.println(x); // デバッグ用 dispNNRNN(x); // 00.00形式で表示 lcd.print("W "); x = panelCurrent; // 電流 dispNRNNN(x); // 0.000形式で表示 lcd.print("A"); } void AcDisp(){ unsigned long x; // AC電力表示 lcd.setCursor(0,0); lcd.print("AC OUT "); x = (panelPow + 50) / 100; // 10mW単位に変換 x = (x * 3) / 4; // 効率75%で変換 dispNNRNN(x); // 00.00形式で表示 lcd.print("W "); // 今日の積算電力表示 lcd.setCursor(0,1); lcd.print("Today "); // x = (SumPowToday) / 3600UL; //0.01W単位に換算 x = TodayCharge; dispNNNRNN(x); // 0000.00形式で表示 lcd.print("Wh"); // 文字位置はあとで修正 } void lcdDisp(){ // モード番号により表示を選択 dispInterval++; if(eepSaveFlag == true){ // EEPROM更新が必要なら if(dispInterval == 10){ // 1サイクル内で終わらないので eepWL(SumChargeAdr, SumCharge); // 分割して書き込み(累積値) } if(dispInterval == 20){ eepWL(TodayChargeAdr, TodayCharge); // (今日の合計値) eepSaveFlag = false; // フラグを消す } } if(dispInterval == 50){ // 表示更新インターバル(サイクル数) switch(dispModeN){ case 0: lcd.setCursor(0,0); lcd.print("RUN"); // dispNN(PWu/100); // パルス幅表示 10uステップ // lcd.print(" "); aragoDisp(); // アラゴの円盤もどきの表示 //5 lcd.print(" "); dispNNRN(panelVoltage); lcd.print("V"); lcd.setCursor(0,1); dispNNNRNN(TodayCharge); lcd.print("/"); dispNNNNNRN((SumCharge+5)/10); lcd.print("Wh"); // break; case 1: SolorDisp(); // ソーラーパネル出力表示 break; case 2: AcDisp(); break; case 3: // 合計表示 lcd.setCursor(0, 0); lcd.print("Total "); lcd.setCursor(0,1); dispXRNN(SumCharge); lcd.print("Wh "); // 文字位置はあとで修正 break; case 4: // デバッグ用表示 lcd.setCursor(0, 0); lcd.print("mp_c="); lcd.print(mppt_c); lcd.print(" d_c"); lcd.print(dispInterval); lcd.setCursor(0,1); lcd.print("V="); lcd.print(analogRead(VoltagePin)); lcd.print(" I="); lcd.print(analogRead(CurrentPin)); lcd.print("-"); lcd.print(Ioff); lcd.print(" "); break; case 5: // 電源周期表示 lcd.setCursor(0, 0); lcd.print("AC period "); lcd.setCursor(0,1); lcd.print(ACinterval); lcd.print("us "); break; // default: } dispInterval = 0; } } int trigCheck(){ // トリガ周期チェック int x=0; MicrosNow=micros(); if(freqFlag == true){ // 初回はチェックしない(出来ない) ACinterval=MicrosNow-MicrosLast; if(ACinterval < 1000000UL){ // 変数のオーバーフロー対策のため1秒以下の場合だけチェック if( ( ACinterval > 20500U) || ( ( ACinterval < 19500U) ) == true) { x = 1; // 周期異常! } } } freqFlag = true; MicrosLast = MicrosNow; return x; // 正常なら0、異常なら1を返す } void eepWL( int a, unsigned long x){ // EEPROM Write Long4バイトの値をEEPROMの指定アドレスに書き込み unsigned long data; data = x >> 24; EEPROM.write(a,data); data = (x & 0x00FF0000) >> 16; EEPROM.write(a + 1, data); data = (x & 0x0000FF00) >> 8; EEPROM.write(a + 2, data); data = x & 0x000000FF; EEPROM.write(a + 3, data); } unsigned long eepRL(int a){ // EEPROM Read Long unsigned long data, x; x = 0; data = EEPROM.read(a); x = data << 24; data = EEPROM.read(a + 1); x = x | (data << 16); data = EEPROM.read(a + 2); x = x | (data << 8); data = EEPROM.read(a + 3); x = x | data; return x; } void eepWI(int a, unsigned int x){ // 16ビットでEEPROMに書き込み unsigned int data; data = (x & 0xFF00) >> 8; EEPROM.write(a, data); data = x & 0x00FF; EEPROM.write(a+1, data); } unsigned int eepRI(int a){ // 16ビットでEEPROMから読み出し unsigned int data, x; x = 0; data = EEPROM.read(a); x = x | (data << 8); data = EEPROM.read(a + 1); x = x | data; return x; } void eepDump(){ // EEPROMに記録された送電量をダンプ unsigned int x; for(int j = 128; j <=1022; j= j + 2){ Serial.print((j - 126) / 2); // 連番 Serial.print(", "); x = eepRI(j); Serial.print(x / 100); // 上位 Serial.print("."); Serial.print(( x / 10) % 10); // 小数点以下 Serial.println(x % 10); } // for } // eepDump void saveTodayCharge(){ // 本日の発電量を保存 unsigned int data; data = TodayCharge; // メモリ節約のため16ビットで記録 for(int i =1020; i >= 128; i= i - 2){ // アドレス範囲は 128〜1023 (データ数は448日分) EEPROM.write(i+2, EEPROM.read(i)); // 旧データーを後ろにずらす EEPROM.write(i+3, EEPROM.read(i+1)); } data = TodayCharge; // 16ビットに縮退 EEPROM.write(128, data >> 8); // ビッグエンディアンで記録 EEPROM.write(129, data & 0x00FF); // } unsigned int VoltMeasure() { // パネル電圧の取得 unsigned long x; adcV=analogRead(VoltagePin); // adcVの値はmpptで使用 x = ((adcV * VC)+256) >> 9; // 分圧比:0.2323を簡易換算。単位は0.1V return x; } void aragoDisp(){ // アラゴの円盤もどき表示、右端の値は600000 byte p; for(int i = 0; i <= 5; i++){ if(arago/100000UL == i){ // 600000/6=100000 p = ((arago / 20000UL)%5) + 1; // 100000/5=20000 lcd.write(p); // 出力を擬似グラフィック表示 } else{ lcd.print(" "); } } } void dispNNRNN(unsigned int x ){ // 00.00形式で数値表示 if(x < 1000){ lcd.print(" "); // ゼロサプレス } lcd.print( x / 100); // 小数点以上 lcd.print("."); lcd.print((x % 100) / 10); // 小数点以下 lcd.print(x % 10); } void dispNN(int x){ // 00形式で表示 if(x <10){ lcd.print("0"); // ちらつくのでゼロサプレスしない } lcd.print(x); } void dispNNRN(unsigned int x){ // 00.0形式で数値表示 if(x < 100){ lcd.print(' '); // ゼロサプレス } else{ lcd.print(x / 100); } x = x % 100; lcd.print( x / 10); lcd.print('.'); lcd.print(x % 10); } void dispNRNNN( unsigned int x){ // 0.000形式で数値表示 lcd.print(x / 1000); lcd.print('.'); lcd.print((x % 1000) / 100); lcd.print((x % 100) / 10); lcd.print(x % 10); } void dispNNNRNN(unsigned long x){ // 0000.00形式で表示 if(x < 10000){ // 100の位 lcd.print(" "); // ゼロサプレス } else{ lcd.print(x/10000); } if(x < 1000){ // 10の位 lcd.print(" "); } else{ lcd.print( (x / 1000) % 10); } // 10の位 lcd.print((x / 100) % 10); // 1の位 lcd.print("."); // 小数点 lcd.print((x /10) % 10); lcd.print(x % 10); } void dispXRNN(unsigned long x){ // X.00形式で表示(数値の上限は無制限) lcd.print(x /100); lcd.print("."); // 小数点 lcd.print((x /10) % 10); lcd.print(x % 10); } void dispNNNNNRN(unsigned long x){ // 0000.00形式で表示 x = x % 1000000UL; // max=99999.9つまり、100kWh以上はゼロに戻る if(x < 100000UL){ // 10000の位 lcd.print(" "); // ゼロサプレス } else{ lcd.print(x/100000UL); } if(x < 10000){ // 1000の位 lcd.print(" "); // ゼロサプレス } else{ lcd.print((x/10000)%10); } if(x < 1000){ // 100の位 lcd.print(" "); } else{ lcd.print( (x / 1000) % 10); } if(x < 100){ // 10の位 lcd.print(" "); } else{ lcd.print( (x / 100) % 10); } lcd.print((x / 10) % 10); // 1の位 lcd.print("."); // 小数点 lcd.print(x % 10); } //エラー処理ルーチン void freqError(int t){ // AC周波数異常 digitalWrite(TransONpin, LOW); // メイントランスOFF lcd.clear(); lcd.print("Freq. Error"); lcd.setCursor(0, 1); lcd.print(t); // lcd.print("us"); eepWL(SumChargeAdr, SumCharge); // 累積発電量を保存 eepWL(TodayChargeAdr, TodayCharge); // 今日の発電量 for(;;){ // 無限ループ } } // タイマー割り込みでパワーFETを操作 void TT1() { digitalWrite(13, HIGH); digitalWrite(PgatePin, HIGH); // ポジ側ON Timer1.setPeriod(PWu); // パルス幅セット Timer1.attachInterrupt( TT2 ); // } // TT1 void TT2() { digitalWrite(13, LOW); digitalWrite(PgatePin, LOW); // ポジ側OFF Timer1.setPeriod(t2); // 次のパルスを出すタイミングセット Timer1.attachInterrupt( TT3 ); // } // TT2 void TT3() { digitalWrite(13, HIGH); digitalWrite(NgatePin, HIGH); // ネガ側ON Timer1.setPeriod(PWu); // パルス幅セット Timer1.attachInterrupt( TT4 ); // } // TT3 void TT4() { digitalWrite(13, LOW); digitalWrite(NgatePin, LOW); // ネガ側OFF Timer1.stop(); // 割り込み終了 } // TT4 // 液晶の外字設定 void defGaiji(){ byte bar1[8] = { // B00000, B10000, B10000, B10000, B10000, B10000, B00000, }; lcd.createChar(1, bar1); byte bar2[8] = { // B00000, B01000, B01000, B01000, B01000, B01000, B00000, }; lcd.createChar(2, bar2); byte bar3[8] = { // B00000, B00100, B00100, B00100, B00100, B00100, B00000, }; lcd.createChar(3, bar3); byte bar4[8] = { // B00000, B00010, B00010, B00010, B00010, B00010, B00000, }; lcd.createChar(4, bar4); byte bar5[8] = { // B00000, B00001, B00001, B00001, B00001, B00001, B00000, }; lcd.createChar(5, bar5); }