syouwa-taroのアイコン画像
syouwa-taro 2024年07月03日作成 (2024年07月05日更新)
製作品 製作品 閲覧数 158
syouwa-taro 2024年07月03日作成 (2024年07月05日更新) 製作品 製作品 閲覧数 158

時計サーバーJSTデーターによる太陽追尾装置第二弾

時計サーバーJSTデーターによる太陽追尾装置第二弾

概要
太陽の位置をthermoセンサで追尾する方法は雲などあると不安定なことがわかり、月・日・現時刻から太陽の位置を計算して追尾する方法を試みた。結果JSTタイムサーバーの時間データーから年間太陽の位置を計算し方位角サーボ 高度サーボをコントロールしてコンパクトに安定した太陽追尾装置を作ることが出来た。ハードもソフトもシンプルでざっと動いたので第二弾紹介します。(実は当初は太陽位置を現時刻から計算式を使いリアルタイムに計算し追尾しようとしたが、三角関数のお化け計算式には歯が立たず玉砕、結局ズルして計算結果のtableを作りlookupしながら簡単赤道儀で太陽追尾しました)

動作
①ESP32のntp(ネットワーク・タイム・プロトコル)機能を使いタイムサーバーから現時刻 月日を取り込む。  
②太陽の位置を春夏秋冬 日の出 日の入りを年間で計算したテーブルを作成しておき、JST現時刻からlookupして
 現在の太陽の位置を特定(リアルタイムで太陽位置計算できなかったので、簡単赤道儀を作り単純にサーボを回転
 するだけで追尾している)
③東西サーボで太陽の方位角を設定。東から西に移動する太陽を4分に1度赤道儀を回転させ日周追尾する 
 北南サーボで太陽の高度を設定し年周追尾する

ここに動画が表示されます

動作詳細
 
 ーー日周追尾ーー
 日の出:15分前になると日の入りに向いていた太陽パネルを日の出方向に戻し日の出を待機
     JSTで日の出時刻になると日周追尾開始
 日中 :太陽パネルを東から西に北極星を軸とした南側円を描きJSTで4分に1度回転(中学理科の太陽運動より)
 日の入り:JSTで日の入り時刻になると日周運動終了
 ーー年周追尾ーー
 春夏秋冬で変化する太陽高度をJSTの月日データから設定(年データから閏年を含めた高精度な位置計算も可能)

簡単赤道儀の構造と動き
東西サーボで現時刻による日周追尾 
     東西サーボでパネル方位を東から西に回転させる構造
キャプションを入力できます
北南サーボで月日データによる年周追尾
      北南サーボでパネル高度を南北に設定する構造
キャプションを入力できます

《参考 迫力ある計算式例(>_<)》
当初は太陽位置をリアルタイムで計算し赤道儀構造を使わず直角に配置した方位と高度サーボだけでSolarTrackeしようと挑戦したが、三角関数の計算が難解で撃沈 今後の課題(計算式太陽の高度と方位角)。
キャプションを入力できます
省電力
ESP32電流値は80mA で かなり多く,夜間など動作していないときはdeep_sleep状態(2mA)にして省電力を図っている。
①日の出から日の入り:1分毎に50秒sleep 10秒 サーボ設定動作
②日の入りから翌日日の出15分前:太陽の出ていない夜間は全てdeep_sleep
③日の出15分前から日の出:パネルを前日の西の日の入り角度から東の日の出の角度に戻し15分間は動作状態
④サーボは動作していない状態でも4mA/2個流れていた。動作していないとき電源を切る方法が課題
⑤SG90 servo libraly は便利であるが、sleepからwakeupしてlibralyを呼ぶとたまにPWMが乱れる現象がありどうしても改善出来ず Timer1 delayによる素朴なPWMを作りこちらを使っている

シリアルモニタ
ESP32の追尾状態はArduinoシリアルモニタでdebugを兼ね確認できる。  
SunSet時間になりdeepsleepに入る時のモニタ例
部品

部品名 備考
ESP_WROOM_32 書き込み、wifi受信がうまくいかない時ネットの対策情報が参考になります 秋月
SG90 サーボ 2個  秋月
18650 Liバッテリ 9900mAh(容量計算している訳ではなく実働時間未確認)
TP4056充電モジュール 過充電防止機能付きモジュール(1860-MICRO) aliexpress
ソーラーパネル 5V 2W (容量計算している訳ではなく手元にあった物)

回路図
キャプションを入力できます

SolarTrackeronJST

#include <time.h> #define JST 3600* 9 #include <ESP32Servo.h> const char* ssid = "************"; // Change this to your WiFi SSID **注意2.4Gのみ対応** const char* pass = "************"; // Change this to your WiFi password int JSTIM; //日本標準時                  【単位:分】 float NSAGLsh ; //float NSAGLsh ; //南中高度角:solar height(サーボコントロールは整数で行う)【単位:度】 int EWAGLsr ; //Sun_Rise方位角:EastWestAngle_sunrise   【単位:度】 int EWAGLsrt ; //Sun_Rise時刻:East/WestAngle_sunrise_time 【単位:分】 int EWAGLsst ; //Sun_Set時刻:East/WestAngle_sunset_time 【単位:分】 int EWAGLsrtt; //明日のSun_Rise時刻              【単位:分】 int RA ; //Sun_Rise方位 ReverseAngle step by step from Sun_Set signed int EWservoAGL ; //EWservo 角度 【単位:度】 signed int NSservoAGL ; //NSservo 角度 【単位:度】 const int EWSERVO_PIN = 13; const int NSSERVO_PIN = 27; //GIOP12,14,15 OUTPUT NG? GIOP27 OUTPUT OK? void NSmotor_servo(int angleNS) { //SG90 サーボ関数 sleepの後複数動作させるとPWM乱れることあり Timer1使う int pulse_widthNS; int starttimeNS; pulse_widthNS = 500+angleNS*10.556; starttimeNS = millis(); while(true) { if(millis()-starttimeNS > 1500) break;//PWM出力時間(400/20ms周期=20Pulse SG90回転速度は0.12秒/60度=2ms/1°) digitalWrite(NSSERVO_PIN, HIGH); delayMicroseconds(pulse_widthNS); digitalWrite(NSSERVO_PIN, LOW); delay(20); } } void EWmotor_servo(int angleEW) { int pulse_widthEW; int pulse_widthEWR; int starttimeEW; pulse_widthEWR = round(500+angleEW*10.556); starttimeEW = millis(); while(true) { if(millis()-starttimeEW > 2000) break;//PWM出力時間(400/20ms周期=20Pulse SG90回転速度は0.12秒/60度=2ms/1°) digitalWrite(EWSERVO_PIN, HIGH); delayMicroseconds(pulse_widthEWR); digitalWrite(EWSERVO_PIN, LOW); delay(20); } } void setup() { Serial.begin(9600); WiFi.begin(ssid, pass); while(WiFi.status() != WL_CONNECTED){ delay(500); } Serial.println("WiFi Connected"); configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); pinMode(EWSERVO_PIN,OUTPUT);//EWservo 制御pin pinMode(NSSERVO_PIN,OUTPUT);//NSservo 制御pin //EWmotor_servo(90);delay(1000); //TEST設定**********************************90°方位角EWサーボ パネルは真南方向 //NSmotor_servo(0); delay(30000);//TEST設定**********************************0°NS高度サーボ パネルは真南水平方向 RA = 270 ; //方位角カウント:count reset=Sun_Set西方位 } //set up END void loop() { time_t t; struct tm *tm; static const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"}; t = time(NULL); tm = localtime(&t); //*****1分周期のLOOP/0秒基準********************************************************************** //****************************************************************************************** if (tm->tm_sec == 0) //1分周期のLOOP/0秒基準 { Serial.printf(" %04d/%02d/%02d(%s) %02d:%02d:%02d\n", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, wd[tm->tm_wday], tm->tm_hour, tm->tm_min, tm->tm_sec); //************************************************データー計算値Table(月ごとに一つの代表値)******************************************** //**************************************************************************************************************************** //                         LookUpTableから月ごとのデーター読みだしサーボ設定 // **(冬至12月21日 春分3月20日 夏至6月21日 秋分9月22日)=毎月20日を代表値 横浜:北緯35.43 東経139.65 **   //-------------------------------------------------------------------------------------------------------- // 月   南中高度 ;   Sun_Rise方位;    Sun_Rise時刻;      Sun_Set時刻;         【単位 分】 switch(tm->tm_mon+1) { case 1: NSAGLsh = 34.05 ; EWAGLsr = 115 ; EWAGLsrt = 6*60+53 ; EWAGLsst = 16*60+51 ; break; case 2: NSAGLsh = 43.21 ; EWAGLsr = 104 ; EWAGLsrt = 6*60+28 ; EWAGLsst = 17*60+23 ; break; case 3: NSAGLsh = 53.95 ; EWAGLsr = 91 ; EWAGLsrt = 5*60+51 ; EWAGLsst = 17*60+48 ; break; //春分の頃 case 4: NSAGLsh = 65.71 ; EWAGLsr = 76 ; EWAGLsrt = 5*60+8 ; EWAGLsst = 18*60+13 ; break; case 5: NSAGLsh = 74.33 ; EWAGLsr = 65 ; EWAGLsrt = 4*60+39 ; EWAGLsst = 18*60+37 ; break; case 6: NSAGLsh = 78.00 ; EWAGLsr = 61 ; EWAGLsrt = 4*60+31 ; EWAGLsst = 18*60+54 ; break; //夏至の頃 case 7: NSAGLsh = 75.40 ; EWAGLsr = 64 ; EWAGLsrt = 4*60+45 ; EWAGLsst = 18*60+50 ; break; case 8: NSAGLsh = 67.28 ; EWAGLsr = 74 ; EWAGLsrt = 5*60+8 ; EWAGLsst = 18*60+22 ; break; case 9: NSAGLsh = 55.93 ; EWAGLsr = 88 ; EWAGLsrt = 5*60+31 ; EWAGLsst = 17*60+39 ; break; //秋分の頃 case 10:NSAGLsh = 44.43 ; EWAGLsr = 102 ; EWAGLsrt = 5*60+51 ; EWAGLsst = 16*60+57 ; break; case 11:NSAGLsh = 34.99 ; EWAGLsr = 114 ; EWAGLsrt = 6*60+26 ; EWAGLsst = 16*60+29 ; break; case 12:NSAGLsh = 31.13 ; EWAGLsr = 119 ; EWAGLsrt = 6*60+51 ; EWAGLsst = 16*60+27 ; break; //冬至の頃 default: break; } JSTIM = tm->tm_hour*60 + tm->tm_min ; // 現時刻【単位:分】 Serial.printf("Sun_Rise/現時刻/Sun_Set:%03d/%03d/%04d 【単位:分】\n",EWAGLsrt,JSTIM,EWAGLsst); //その日の高度をNSサーボにセット NSservoAGL = round(NSAGLsh) ; NSmotor_servo(NSservoAGL); delay(50); //南中高度設定 EWservoPWMは50ms後out Serial.print("南中仰角:");Serial.print(NSAGLsh);Serial.print("° (servo"); Serial.print(NSservoAGL);Serial.println("°)"); //**************************************【Sun_Rise15分前➤Sun_Rise】************************************************************* //**************************************************************************************************************************** //Sun_Rise15分前wakeupすると方位角を 前日Sun_Set方位 から-1°/1秒づつ戻して 本日Sun_Rise方位 に設定(サーボは90°ずれているので-90補正)してSun_Riseを待つ if (JSTIM < EWAGLsrt ) // Sun_Rise15分前からSun_Rise直前 TEST設定 if(JSTIM > EWAGLsrt)*********Sun_RiseTEST************* { if (RA == 270 ) //EWサーボをSun_Rise方位角まで戻すカウントスタート { //EWサーボを昨日Sun_Set角度から今日のSunRise方位角まで戻すカウントスタート if (( EWAGLsr + (EWAGLsst - EWAGLsrt)/4 ) >= 270 ) RA = 270 ; //昨日のSun_Set角が真西270°以上はサーボのspec超えているので戻り開始角RA=西270°(servo0°) else RA = EWAGLsr + (EWAGLsst - EWAGLsrt)/4 ; //昨日のSun_Set角が真西270°以内なので戻り角開始角RA=昨日のSun_Set角 while( (RA >= EWAGLsr)&&(RA >= 90) ) //昨日Sun_Set方位角 ➤ 本日Sun_Rise方位角 に戻す  { //Sun_Rise角が真東90°以上はサーボのspec超えているので戻り角戻り角は真東90°まで //Sun_Rise角が真東90°以内は戻り角はSun_Rise角まで EWservoAGL = 180 - (RA-90) ; //昨日のSun_Setから1°づつ今日のSun_Rise方位に戻る (サーボ回転逆補正:180°・角度補正90°) EWmotor_servo(EWservoAGL) ; //RA:北0°(s-)東90°(s180°)南180°(s90°)西270°(s0°)北360°(servo Angle) Serial.printf("Sun_Rise方位%d° 現方位%d°(servo%d°)\n",EWAGLsr,RA,EWservoAGL); delay(1000); //1°/1secでSun_Rise角まで戻す(servo specは1°/2ms) RA-- ; } delay(1000); } //Sun_Riseを待つ Serial.printf("Sun_Riseまであと%d分\n\n", EWAGLsrt-JSTIM ); delay(2000); //表示安定 } //*****************************************【Sun_Rise ➤ Sun_Set】********************************************************* //************************************************************************************************************************ else if (JSTIM < EWAGLsst) //Sun_RiseからSun_Set { if (JSTIM == EWAGLsrt) { Serial.println(""); Serial.printf("🌅 Good morning!今日は%d月%d日 現在%d時%d分\n",tm->tm_mon+1,tm->tm_mday,tm->tm_hour,tm->tm_min); } if ((EWAGLsr + (JSTIM - EWAGLsrt)/4 ) < 90) { EWservoAGL = 180; //現在の方位角が90°以下であればEWサーボのspec外なので方位角90°(EWサーボ(180°))とする } else if ( (EWAGLsr + (JSTIM - EWAGLsrt)/4) > 270) { EWservoAGL = 0 ; //現在の方位角が270°以上であればEWサーボのspec外なので方位角270°(EWサーボ(0°))とする } else { EWservoAGL = 180 - (EWAGLsr + (JSTIM - EWAGLsrt)/4 - 90); //(サーボ回転方向反転180°-(Sun_Rise方位角 +1°X(JST-Sun_Rise時刻)/4分 - サーボ補正90°) } EWmotor_servo(EWservoAGL); Serial.printf("🌞方位角:Sun_Rise%d°➤現在計算%d°(実方位%d° servo%d°) ✙1°まで50秒x4回(-_-)zzz\n\n",EWAGLsr,EWAGLsr+(JSTIM - EWAGLsrt)/4,270-EWservoAGL,EWservoAGL ); delay(2000); //表示転送時間確保 esp_sleep_enable_timer_wakeup(50*1000000); //方位を1°/4分 西にすすめる:servo確実に設定する為(50秒sleep+10秒servo設定)x4回try=方位角1°回転   esp_light_sleep_start(); //(-_-)zzz 230sec ***deep sleepではごくまれにWifi connect missが発生でlight sleepが安心?*** } //Sun_RiseからSun_Setまで終了 else //Sun_Set後 deep_SLEEP { //***********************************【Sun_Set➤sleep➤Sun_Rise5分前】*********************************************************** //***************************************************************************************************************************** //Sun_Set時刻になると方位角動き停止してSun_Rise15分前までDeepSleep Serial.printf("🌄SunSet! 明日のSun_Rise%d時%d分の15分前 方位%d°まで%d時間%d分 Good Night(-_-)zzz\n\n",EWAGLsrtt/60,EWAGLsrt%60,EWAGLsr,((1440-JSTIM)+EWAGLsrt-30)/60,((1440-JSTIM)+EWAGLsrt-30)%60); delay(200); //表示転送時間確保 int nightsleep=((1440-JSTIM)+EWAGLsrt-15)*60 ; // (深夜零時までの時間:60分X24時間ー現時刻)+明日のSun_Rise時刻 - 5分前)X60秒= Night_sleep時間 esp_sleep_enable_timer_wakeup(nightsleep*1000000ULL); // Sun_Set(現時刻)からSun_Rise15分前までTimer_Deep_SLEEP    esp_deep_sleep_start() ; //(-_-)zzz ALL_Night } } //1分周期LOOP END } //LOOP END

感想
太陽追尾を現時刻から太陽の方位をリアルタイムで計算し追尾する方法を目指したが計算式が手に負えず(ズルして)計算結果をtableにしてlookupし簡単赤道儀でなんとか実現した。結果雲で見え隠れする太陽も安定して追尾出来るようになった。今後錆びた脳みそに鞭打って、太陽軌道計算が解ければ、メカ赤道儀不要なソフト赤道儀のコンパクトな太陽追尾装置など実現したい。

小学生の頃ソーラーカーを作り太陽で見事走り感激しましたがすぐに停止(>_<) 理由は車が動きソーラーの向きが太陽から少しでもずれると発電が弱まりすぐ止まってしまいます。この悔しさが太陽追尾装置の興味につながっているようです。ソーラーパネルは太陽追尾することで効率向上し発電コストも下がり、今後コンパクトな追尾装置の開発、導入を期待です。

参考にさせていただいた資料
太陽の日周運動
太陽の高度と方位角
赤道儀を作っちゃおう!
ESP32で現在時刻を取得する
ArduinoでマイクロサーボSG90を動かしてみる
ESP32にプログラムを書き込めない!!

  • syouwa-taro さんが 前の水曜日の23:00 に 編集 をしました。 (メッセージ: 初版)
  • syouwa-taro さんが 前の木曜日の7:16 に 編集 をしました。
  • Opening
    mipsparcのアイコン画像 mipsparc 前の木曜日の14:10

    すごいです!
    リアルタイムで計算せずあらかじめ計算した表を参照する方法は、原始的に思えるかもしれませんが、実はコンピューティングの世界では賢い方法として古来から現代まで使われています。特に組み込みなど。

    0 件の返信が折りたたまれています
  • Opening
    mipsparcのアイコン画像 mipsparc 前の木曜日の14:14

    ちなみにですが、ご存知かもしれませんが、ソーラーパネル上には輪ゴムなど含めてなにも遮蔽物を置かないのが原則です。
    発電できなかった部分は抵抗となり、一気に効率が下がります。輪ゴム程度であれば無視できるレベルかも知れませんが、念の為。

    syouwa-taroのアイコン画像 syouwa-taro 前の金曜日の15:55

    アドバイスありがとうございます。試作を動かす事がやっとだったこともあり、これから完成度をブラシュアップさせてまいります。

    1 件の返信が折りたたまれています
  • Opening
    Mikenekoのアイコン画像 Mikeneko 前の金曜日の15:47

    素晴らしい方法です。できることが同じならなるべく簡単な方が当然いいです。そういう意味でこのやり方は賢い方法だと思います。素晴らしいの一言です。

    0 件の返信が折りたたまれています
  • Opening
    syouwa-taroのアイコン画像 syouwa-taro 前の金曜日の16:07

    コメントありがとうございます。基本原理は簡単で空は北極星を軸とした円が一日一回転しているので、サーボモーターの軸を北極星に向け日の出から日の入りまで(PCで読み込んだJST)日本標準時に合わせ180°回転させています。基本動作に緯度 年間の太陽高度の変化を組み込んでいます。ESP32によりハード ソフト シンプルに作ることが出来たのでご紹介させていただきました。

    syouwa-taroのアイコン画像 syouwa-taro 前の金曜日の16:15

    すいません、語句訂正です 誤:PCで読み込んだ ➤ 正:ESP32で読み込んだ

    1 件の返信が折りたたまれています
  • syouwa-taro さんが 前の金曜日の16:23 に 編集 をしました。 (メッセージ: 文字表示の行の整頓)
ログインしてコメントを投稿する