時計サーバーJSTデーターによる太陽追尾装置第三弾 軌道計算によるソフト赤道儀方式
概要
先回JSTにより軌道計算を行い太陽追尾しようとしたが難解な計算が出来ず”ズル”をして計算結果をlookupテーブルにし,更に簡単な赤道儀をサーボで動かし装置を作った。今回錆びた脳みそに鞭打ってESP32を使いArduinoによるリアルタイムの軌道計算に挑戦し、なんとか高度 方位角が誤差3度以内まで詰まり、構造も直角に組んだ2つのシンプルなサーボのソフト赤道儀を実現し太陽追尾装置を作った。
第二弾からの修正点
- 先回は太陽の動きを軌道計算結果のtableを作り追尾していたが、今回はESP32でJSTデーターからリアルタイムに高度 方位を計算し2つのサーボを動かし追尾を行った。
- 先回は簡単な赤道儀を使い2つのサーボで動かし構造的に計算を単純化したが、今回は高度と方位角の2つのサーボを水平と垂直に組んだシンプルな構造で赤道儀の動きをソフトで実現している。
- 回路 部品は先回と全く同じで、サーボの組み方だけが違います。
計算に使わせていただいた計算式
太陽の高度と方位角(地表面上の太陽軌跡図、JavaScript版)
2つのサーボの構造
高度サーボと方位角サーボを直交して組み、サーボはホビー用SG90(0度~180度仕様)を使い高度は天空:0°~180°をカバーしますが,方位角は東西南北の半分(作品は東~南~西)をカバー、およそ1度stepで動かす。方位角サーボの軸にはめ込んでいる高度サーボが回転する面を南になるよう設置します。
TESTソフト
スタートのsetupで 2つのサーボ本体とアームの組み立て角度確認の為 日の出の時パネルが東を向いた状態設定で30秒停止しています(写真の状態 方位角サーボ=180° 高度サーボ=0°)確認後はコメントアウト。
TimeLapseによるソフト赤道儀の日中の動き
参考にさせて頂いた資料
Arduino-三角関数
atan2関数
cos関数
Arduino-Trigonometry関数(三角関数)の使い方
Arduino-数学ライブラリ
ArduinoのSketch(スケッチ)で使用できるデータ型
サーボモータのスピードを制御します。ライブラリは使いません
SolarTrackeJST3
#include <WiFi.h>
#include <time.h>
#define JST 3600* 9
#define PI 3.141592653589793
const char* ssid = "**********"; // Change this to your WiFi SSID /2.4G
const char* pass = "**********"; // Change this to your WiFi password
const float NL = 35.43 ; // Change this to your North longitude default:KANAGAWA
const float EL = 139.65 ; // Change this to your East latitude default:KANAGAWA
int OneLoopMin ;
int JSTIM; //日本標準時 【単位:分】
int EWAGL ; //装置(パネル)方位角 【単位:度】
int UDAGL ; //装置(パネル)高度 【単位:度】
int EWservoAGL; //E/Wサーボ 【単位:度】
int UDservoAGL; //U/Dサーボ 【単位:度】
float eh; //均時差: e(天球上を一定な速さで動くと考えた平均太陽と、実際の太陽との移動の差、17分未満) [単位:時間]
float Hd; //太陽計算高度
float Bd; //太陽計算方位
float ttr;
float t1h; //日の出時刻: [単位:時間]
float t2h; //日の入時刻: [単位:時間]
int EWAGLsrt ; //Sun_Rise時刻:East/WestAngle_sunrise_time 【単位:分】
int EWAGLsst ; //Sun_Set時刻:East/WestAngle_sunset_time 【単位:分】
const int EWservo_PIN = 13;
const int UDservo_PIN = 27; //GIOP12,14,15 OUTPUT PWMNG? GIOP27 OUTPUT PWMOK?
void setup() {
Serial.begin(115200);
delay(10);
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(UDservo_PIN,OUTPUT);//UDservo 制御pin
int OneLoopMin=0; //one loop/1min Flag
//TEST サーボ軸はめ込み角度確認================================================
Serial.print("TEST サーボ軸vsアーム 方向、はめ込み角度確認");
EWmotor_servo(180); //PWM出力時間100ms
EWmotor_servo(180); //PWM出力時間+100ms
Serial.println("方位角サーボ180°(方位90°:東): 軸=垂直/天頂 軸囲りマーク=真東 軸と結合した高度サーボの軸=水平/真南");
delay(1000);
UDmotor_servo(0); //PWM出力時間100ms
UDmotor_servo(0); //PWM出力時間+100ms
Serial.println("高度サーボ0°(高度0°) : 軸=水平/真南 軸囲りマーク=真東 アーム=水平/真東 パネル面=垂直/真東");
delay(30000);//TEST サーボ軸vsアーム設定確認*********************************
Serial.println("TEST30秒終了");
//================================================================
} //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);
JSTIM = tm->tm_hour*60 + tm->tm_min ; // 現時刻【単位:分】時間をずらすことで時間のシュミレーションが出来る。(二時間前:-60X2=-120)
//*****1分周期のLOOP/0秒基準&& Mirror設定********************************************************************************************
//********************************************************************************************************************************
if ((tm->tm_sec == 0)&&(tm->tm_min != OneLoopMin)) //OneLoop/1分周期のLOOP/0秒基準 //=========================================
{
OneLoopMin = tm->tm_min; //Min更新でOneLoop/1Min
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);
//************************************************データー計算*****************************************************
//****************************太陽赤緯 均時差 太陽高度 太陽方位 日の出時刻 日の入り時刻*******************************
//太陽赤緯計算 //太陽赤緯:δ(太陽光線と地球の赤道面との角度、±23°27'の範囲で変化) [単位:度]
float cald ;
float month[12]={0,31,28,31,30,31,30,31,31,30,31,30}; //1月month[0]=0 ~ 11月month[11]=30 11ヶ月配列
float J = 0 ; //元日からの通算日数 + 0.5
for (int i=0; i<=tm->tm_mon; i++) //i=0=1月~ 前月までの合計日数
{
J=J+month[i];
}
J = J+tm->tm_mday+0.5 ; //元日から本日までの通算日数+0.5日
cald=0.33281-22.984*cos(PI*2/365*J)-0.34990*cos(2*PI*2/365*J)-0.13980*cos(3*PI*2/365*J)+3.7872*sin(PI*2/365*J)
+0.03250*sin(2*PI*2/365*J)+0.07187*sin(3*PI*2/365*J); //太陽赤緯
//均時差計算 均時差:e(天球上を一定な速さで動くと考えた平均太陽と、実際の太陽との移動の差、17分未満) [単位:時間]
eh=0.0072*cos(PI*2/365*J )-0.0528*cos(2*PI*2/365*J )-0.0012*cos(3*PI*2/365*J )-0.1229*sin(PI*2/365*J )
-0.1565*sin(2*PI*2/365*J )-0.0041*sin(3*PI*2/365*J );
//太陽高度計算
float td ; //時角[度]
float tr ; //時角[ラジアン]
float Hr ; //計算高度[ラジアン]
float Min=tm->tm_min; float Hou=tm->tm_hour;
td=15*(Hou + Min/60 + (EL-135.00)/15+eh)-180; //時角【度】:東経:EL 0時:-180° 12時:0° 24時:+180°
tr=td/(180/PI);//時角【度】を【ラジアン】に変換
Hr = asin(sin(NL/(180/PI))*sin(cald/(180/PI)) + cos(NL/(180/PI))*cos(cald/(180/PI))*cos(tr)); //高度計算【ラジアン】東経:EL 北緯:NL 時角t=15T-180
Hd = Hr*(180/PI); //高度【ラジアン】を【度】に変換
UDAGL=constrain(Hd,0,180) ; //サーボspec 0~180に設定
//高度サーボ設定
UDservoAGL=UDAGL ;
UDmotor_servo(UDservoAGL);
//方位角計算
float X ;
X = cos(cald/(180/PI))*sin(tr)/cos(Hr); //方位角計算X
float Y ;
Y = (sin(Hr)*sin(NL/(180/PI)) - sin(cald/(180/PI)))/cos(Hr)/cos(NL/(180/PI));//方位角計算Y
float A ;
A = atan2(X ,Y )+PI ; //計算方位角 【ラジアン】
Bd = A*(180/PI); //計算方位角 【度】
EWAGL=Bd; //装置方位角 整数【度】
//方位サーボ設定
if (EWAGL < 90) {EWAGL=90; EWservoAGL=180; //現在の計算方位角が90°以下であればEWサーボのspec外で装置方位90°(EWサーボ(180°))とする(真北:0°)
} else if (EWAGL > 270) { EWAGL=270; EWservoAGL=0 ; //現在の計算方位角が270°以上はEWサーボspec外で装置方位270°(EWサーボ(0°))(真西:270°)とする
} else {EWservoAGL=180-(EWAGL-90); //サーボ回転方向反転180°-(計算方位角 - サーボ補正90°)
}
EWmotor_servo(EWservoAGL); //方位を1°/4分 西にすすめる:servo確実に設定する為(50秒sleep+10秒servo設定)x4回try=方位角1°回転
//日の出時刻計算: t1h [時]
//日の入時刻計算: t2h [時]
ttr = acos(-tan(cald/(180/PI))*tan(NL/(180/PI)));
t1h = (-ttr*(180/PI)+180)/15-(EL-135)/15-eh ;
t2h = (ttr*(180/PI)+180)/15-(EL-135)/15-eh ;
EWAGLsrt=t1h*60; //Sun_Rise時刻【分】
EWAGLsst=t2h*60; //Sun_Set時刻 【分】
Serial.printf("日の出/現時刻/日の入り === %02d:%02d/%02d%:%02d/%02d:%02d 北緯",EWAGLsrt/60,EWAGLsrt%60,JSTIM/60,JSTIM%60,EWAGLsst/60,EWAGLsst%60);
Serial.print(NL);Serial.print("° 東経");Serial.print(EL); Serial.println("°");
Serial.print("太陽赤緯");Serial.print(cald);Serial.print("° 元日からの通算日数+0.5=");Serial.print(J);Serial.print("日 均時差");Serial.println(eh);
Serial.print("現在計算太陽高度");Serial.print(Hd);Serial.printf("°(装置方位%d° servo%d°)",UDAGL,UDservoAGL);
Serial.print(" 計算南中高度/時刻:");Serial.print(90.00-NL+cald);Serial.print("°/");Serial.printf("%02d:%02d\n",(EWAGLsrt/2+EWAGLsst/2)/60,(EWAGLsrt/2+EWAGLsst/2)%60);
Serial.printf("現在計算太陽方位");Serial.print(Bd);Serial.printf("°(装置方位%d° servo%d°)",EWAGL,EWservoAGL);Serial.println("/北0° 東90° 南180° 西270°");
Serial.println(" ");
//**************************************【Sun_Rise5分前➤Sun_Rise】*************************************************************
//*****************************************************************************************************************************
if (JSTIM < EWAGLsrt ) // WakeUpSun_Rise5分前からSun_Rise直前
{
//Sun_Riseを待つ
Serial.printf("Sun_Riseまであと%d分\n\n", EWAGLsrt-JSTIM ); delay(2000); //0秒基準loopで1秒間に複数loopしないよう表示安定
}
//*****************************************【Sun_Rise ➤ Day_Time➤ Sun_Set ➤ deep sleep 】***********************************
else //Sun_Rise以降***********************************************************************************************************
{
//**********************Sun_Rise****************************************************************************************
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);
}
//**********************DayTime*******************************************************************************************
if (JSTIM < EWAGLsst) {
delay(200); //表示転送時間確保
esp_sleep_enable_timer_wakeup(50*1000000); //4分に一度WAKEUPして+1°サーボを進めれば良いが,念のため1分x4回同じangleをサーボに送る
esp_light_sleep_start(); // ***deep sleepではごくまれにWifi connect missが発生でlight sleepが安心?***
} //Sun_RiseからSun_Set前まで終了
//*********************Sun_Set Sleep**************【Sun_Set➤sleep➤Sun_Rise5分前】****************************************
else //Sun_Set時刻になるとパネルをSun_Rise方位(東)に戻しSun_Rise5分前までDeepSleep。
{
Serial.printf("🌄SunSet! %d時%d分パネルを真東0°(方位90°)に戻しSun_Rise%d時%d分の5分前までsleep \n\n",
EWAGLsst/60,EWAGLsst%60,EWAGLsrt/60,EWAGLsrt%60);
while(EWAGL >= 90) //Sun_Set方位角 ➤ 翌日Sun_Rise方向:真東90°(サーボ180°)に戻す
{
EWservoAGL=180-(EWAGL-90);
EWmotor_servo(EWservoAGL);
Serial.printf("Sun_Rise方向東:90°に戻す 現方位%d°(servo%d°)\n",EWAGL,EWservoAGL);
delay(100) ; //1°/1secでSun_Rise角まで戻す(servo specは1°/2ms)
EWAGL-- ; //方位角真西270°(最小servo0°) ➤ 真東90°(最大servo180°)
}
Serial.printf("Good Night(-_-)zzz for %d時間%d分 till 5min befor Sun_Rise \n",((1440-JSTIM)+EWAGLsrt-5)/60,((1440-JSTIM)+EWAGLsrt-5)%60);
delay(2000); //表示転送時間確保
int nightsleep=((1440-JSTIM)+EWAGLsrt-5)*60; // (深夜零時までの時間:60分X24時間ー現時刻)+明日のSun_Rise時刻 - 5分前)X60秒= Night_sleep時間
esp_sleep_enable_timer_wakeup(nightsleep*1000000ULL); // Sun_Set(現時刻)からSun_Rise5分前までTimer_Deep_SLEEP 【単位μsec】
esp_deep_sleep_start() ; //(-_-)zzz ALL_Night
} //Sun_Set deep sleep end
} // Sun_Rise 以降end
} //分周期 0秒同期 SolarTrackeLOOP END
} //LOOP END
void EWmotor_servo(int angle) { //Servo.h Library はsleepとTimer競合するよう?
float pulse_width;
float starttime;
pulse_width = 500.00+angle*10.556;
starttime = millis();
while(true) {
if(millis()-starttime > 100) break; //PWM出力時間100ms( 1.2ms/60°)
digitalWrite(EWservo_PIN, HIGH);
delayMicroseconds(pulse_width);
digitalWrite(EWservo_PIN, LOW);
delay(20);
}
}
void UDmotor_servo(int angle) { //Servo.h Library はsleepとTimer競合するよう?
float pulse_width;
float starttime;
pulse_width = 500.00+angle*10.556;
starttime = millis();
while(true) {
if(millis()-starttime > 100) break; //PWM出力時間100ms( 1.2ms/60°)
digitalWrite(UDservo_PIN, HIGH);
delayMicroseconds(pulse_width);
digitalWrite(UDservo_PIN, LOW);
delay(20);
}
}
課題
- JSTの時間が進む。原因はsleep設定すると起こるのでTIMERが共有されているようです。TIMERが管理出来ればよいですが、ESP32の限界かもしれません。またサーボライブラリも異常数値が出ることがあり別途作成。
- deep sleep時の消費電流が多い。10mA近く電流が流れ原因はサーボには動作していなくとも5mA以上流れています。動作していないときはアースなど電源cutが望ましい。
- ESP32の能力もあると思いますが、計算精度をよりあげたい
- まだパネルから直接スマホ充電する方が効率よいが、これを超えることが目標
感想
ESP32を使いArduinoで三角関数SolarTrackeの計算は初めてでダメ元で挑戦。度数とラジアンの関係もよくわからず何度もあきらめかけたがネットで勉強しながらそのうち計算結果が1~2度以内の誤差で答えが出るようになり、結果でサーボを動かしやっと完成・・・(受験の頃 受験生の歌で”サイン コサイン何になる~~♫ おいらの道がある~♫”と歌ったものでしたが(>_<))
今回のソフトは外部回路がなくともESP32単独で動き、モニタ(115200bps)をみることにより太陽の動きがわかります。WiFi SSIDを記入し(緯度 経度も自分の地域に書き直し)書き込めば、マイコン単体で気象庁などからでは得られない自分の地域の日の出 日の入り時刻や南中時刻 高度がわかります(数分の誤差有<m(__)m> わかってどうする...?)
投稿者の人気記事
-
syouwa-taro
さんが
2024/08/30
に
編集
をしました。
(メッセージ: 初版)
-
syouwa-taro
さんが
2024/08/31
に
編集
をしました。
(メッセージ: youtubeサーボの動きを見やすく)
-
syouwa-taro
さんが
2024/09/03
に
編集
をしました。
(メッセージ: 計算データ更新)
ログインしてコメントを投稿する