時計サーバー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を兼ね確認できる。
部品
部品名 | 備考 |
---|---|
ESP_WROOM_32 | 書き込み、wifi受信がうまくいかない時ネットの対策情報が参考になります 秋月 (注 電圧specはmax3.6vです。回路はLi電池3.7vを接続 実験される場合コンバーターで降圧してください) |
SG90 | サーボ 2個 秋月 |
18650 | Liバッテリ 9900mAh(容量計算している訳ではなく実働時間未確認) |
TP4056充電モジュール | 過充電防止機能付きモジュール(1860-MICRO) aliexpress |
ソーラーパネル | 5V 2W (容量計算している訳ではなく手元にあった物) |
SolarTrackeronJSTv2
#include <WiFi.h>
#include <time.h>
#define JST 3600* 9
#include <ESP32Servo.h>
const char* ssid = "*************"; // Change this to your WiFi SSID
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 【単位:分】
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高度サーボ パネルは真南水平方向
} //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_Rise5分前➤Sun_Rise】*************************************************************
//*****************************************************************************************************************************
if (JSTIM < EWAGLsrt ) // WakeUpSun_Rise5分前からSun_Rise直前 TEST設定 if(JSTIM > EWAGLsrt)*********Sun_RiseTEST*************
{
//Sun_Riseを待つ
Serial.printf("Sun_Riseまであと%d分\n\n", EWAGLsrt-JSTIM ); delay(2000); //0秒基準loopで1秒間に複数loopしないよう表示安定
}
//*****************************************【Sun_Rise ➤ Sun_Set ➤ deep sleep 】*********************************************************
//************************************************************************************************************************
else
{
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);
// SLEEP時間設定***************Sun_Setまで1分毎sleep / Sun_Set後翌日Sun_Riseまでdeep sleep*************************
if (JSTIM < EWAGLsst)
{
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(); // ***deep sleepではごくまれにWifi connect missが発生でlight sleepが安心?***
} //Sun_RiseからSun_Set前まで終了
//***********************************【Sun_Set➤sleep➤Sun_Rise5分前】***********************************************************
else //Sun_Set時刻になるとパネルをSun_Rise方位(東)に戻しSun_Rise5分前までDeepSleep。
{
Serial.printf("🌄SunSet! %d時%d分パネルを東に戻しSun_Rise%d時%d分(方位%d°)の5分前までsleep \n\n",
EWAGLsst/60,EWAGLsst%60,EWAGLsrt/60,EWAGLsrt%60,EWAGLsr);
while( (EWservoAGL <= (270-EWAGLsr))&&(EWservoAGL <= 180) ) //Sun_Set方位角 ➤ 翌日Sun_Rise方位角(最大servo真東180°)に戻す
{ //Sun_Rise角:EWAGLsr(servo=180-(EWAGLsr-90))&&真東180°)まで
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,270-EWservoAGL,EWservoAGL);
// delay(1000) ; //1°/1secでSun_Rise角まで戻す(servo specは1°/2ms)
EWservoAGL++ ; //方位角真西270°(最小servo0°) ➤ 真東90°(最大servo180°)
}
Serial.printf("Good Night(-_-)zzz for %d時間%d分 \n",((1440-JSTIM)+EWAGLsrt-3)/60,((1440-JSTIM)+EWAGLsrt-3)%60);
delay(2000); //表示転送時間確保
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_Rise3分前までTimer_Deep_SLEEP 【単位μsec】
esp_deep_sleep_start() ; //(-_-)zzz ALL_Night
} //Sun_Set deep sleep end
} //Sun_Rise 以降end
} //1分周期LOOP END
} //LOOP END
感想
太陽追尾を現時刻から太陽の方位をリアルタイムで計算し追尾する方法を目指したが計算式が手に負えず(ズルして)計算結果をtableにしてlookupし簡単赤道儀でなんとか実現した。結果雲で見え隠れする太陽も安定して追尾出来るようになった。今後錆びた脳みそに鞭打って、太陽軌道計算が解ければ、メカ赤道儀不要なソフト赤道儀のコンパクトな太陽追尾装置など実現したい。
小学生の頃ソーラーカーを作り太陽で見事走り感激しましたがすぐに停止(>_<) 理由は車が動きソーラーの向きが太陽から少しでもずれると発電が弱まりすぐ止まってしまいます。この悔しさが太陽追尾装置の興味につながっているようです。ソーラーパネルは太陽追尾することで効率向上し発電コストも下がり、今後コンパクトな追尾装置の開発、導入を期待です。
参考にさせていただいた資料
太陽の日周運動
太陽の高度と方位角
赤道儀を作っちゃおう!
ESP32で現在時刻を取得する
ArduinoでマイクロサーボSG90を動かしてみる
ESP32にプログラムを書き込めない!!
投稿者の人気記事
-
syouwa-taro
さんが
2024/07/03
に
編集
をしました。
(メッセージ: 初版)
-
syouwa-taro
さんが
2024/07/04
に
編集
をしました。
Opening
mipsparc
2024/07/04
syouwa-taro
2024/08/11 Opening
mipsparc
2024/07/04
syouwa-taro
2024/07/05 Opening
Mikeneko
2024/07/05 Opening
syouwa-taro
2024/07/05
syouwa-taro
2024/07/05 -
syouwa-taro
さんが
2024/07/05
に
編集
をしました。
(メッセージ: 文字表示の行の整頓)
-
syouwa-taro
さんが
2024/07/10
に
編集
をしました。
(メッセージ: Sleep時間を延ばし省電力化)
ログインしてコメントを投稿するすごいです!
リアルタイムで計算せずあらかじめ計算した表を参照する方法は、原始的に思えるかもしれませんが、実はコンピューティングの世界では賢い方法として古来から現代まで使われています。特に組み込みなど。
コメントありがとうございます。三角関数のプログラムがうまく動かず一時はあきらめかけましたが、計算結果のLookUp_Tableでなんとか動作させることが出来ました。ともかく動くものを作ればまた次のチャレンジにもつながりとりあえずよかったです。その後太陽赤緯δ 太陽高度の計算も少しずつ動き始めまた次の作品をご紹介できればと思っております。
ちなみにですが、ご存知かもしれませんが、ソーラーパネル上には輪ゴムなど含めてなにも遮蔽物を置かないのが原則です。
発電できなかった部分は抵抗となり、一気に効率が下がります。輪ゴム程度であれば無視できるレベルかも知れませんが、念の為。
アドバイスありがとうございます。試作を動かす事がやっとだったこともあり、これから完成度をブラシュアップさせてまいります。
素晴らしい方法です。できることが同じならなるべく簡単な方が当然いいです。そういう意味でこのやり方は賢い方法だと思います。素晴らしいの一言です。
コメントありがとうございます。基本原理は簡単で空は北極星を軸とした円が一日一回転しているので、サーボモーターの軸を北極星に向け日の出から日の入りまで(PCで読み込んだJST)日本標準時に合わせ180°回転させています。基本動作に緯度 年間の太陽高度の変化を組み込んでいます。ESP32によりハード ソフト シンプルに作ることが出来たのでご紹介させていただきました。
すいません、語句訂正です 誤:PCで読み込んだ ➤ 正:ESP32で読み込んだ