Привет, Хабр!
Решил показать свою небольшую самоделку, которая работает примерно так:
Если КДПВ сделала свое дело — тогда добро пожаловать под кат 🙂
Небольшой спойлер
Я старался писать так, чтобы было максимально понятно всем
Аппаратная часть
Итак, для создания минимально работающего прототипа нам понадобятся:
По сборке особых хитростей нет: просто закрепить все компоненты на основании термоклеем.
Только надо постараться чтобы оси сервоприводов и лазера были максимально перпендикулярны друг другу, это уменьшит погрешность наведения.
Подключение
Тут тоже ничего сложного:
- GPS подключается по uart (понадобится только Rx, так как нам ничего не нужно отправлять на модуль
- сервоприводы – в пины 10 (ось азимута) и 11 (ось высоты)
- кнопка – во 2 пин
- питание на все модули
- опционально – конденсатор по питанию
Фотографии моей реализации(19Мб)
Программная часть
Переходим к самому интересному.
Весь код можно условно разделить на три части:
Заметки по коду:
Для жпс использована библиотека tinygps++
Для кнопки — GyverButton
Когда жпс понимает где он, на ардуине загорается светодиод на 13м пине
Для примера в коде есть массив с координатами разных ярких звёзд
Исходный код
#include <math.h>
#include <ServoSmooth.h>
#include <GyverButton.h>
#include "TinyGPS++.h"
TinyGPSPlus gps;
ServoSmooth yaw;
ServoSmooth pitch;
GButton but(2);
int yr = 0, mo = 0, d = 0, h = 0, m = 0, s = 0;
float phi = 0, lambda = 0;
float az = 0, height = 0;
int counter = 0;
float alpha = 0.0, delta = 0.0;
float sunalpha = 0, sundelta = 0;
float Coordinates[10][2] =
{
{0, 0},
{sunalpha * 360 / 2 / PI, sundelta * 360 / 2 / PI}, //sun
{297.9458, 8.9233}, //altair
{279.4083, 38.8038}, //vega
{310.5333, 45.3538}, //deneb
{79.55, 46.0163},//capella
{89.0708, 7.4092}, //betelgeuse
{152.3625, 11.8672}, //regul
{51.4458, 49.9319} //mirfak
};
int Days(int d, int m, int y)//тут считаем, сколько дней прошло с момента весеннего равноденствия (21.03)
{
int days = 0;
int yearNotLeap[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int yearIsLeap[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
{
for (int i = 0; i < m — 1; i++)
{
days += yearIsLeap[i];
}
}
else
{
for (int i = 0; i < m — 1; i++)
{
days += yearNotLeap[i];
}
}
if (m == 1) {
return d — 81;
}
else
return days + d — 81;
}
void GpsGetData()
{
while (Serial.available() > 0)
gps.encode(Serial.read());
if (gps.location.isUpdated())
{
phi = gps.location.lat(); //Широта в градусах (double)
lambda = gps.location.lng(); // Долгота в градусах (double)
yr = gps.date.year(); // Год (2000+) (u16)
mo = gps.date.month(); // Месяц (1-12) (u8)
d = gps.date.day(); // День (1-31) (u8)
h = gps.time.hour(); // Час (0-23) (u8)
m = gps.time.minute(); // Минуты (0-59) (u8)
s = gps.time.second(); // Секунды (0-59) (u8)
}
}
void CalculateParams()
{
float _time = (h + (float)m / 60 + (float)s / 3600) * 360.0 / 24.0;//вычисляем время в часах
float sunlambda = (float)Days(d, mo, yr) * 360 / 365; // вычисляем эклиптическую долготы солнца в градусах
sundelta = asin(0.398749 * sin(sunlambda * 2 * PI / 360));//вычисляем склонение солнца в радианах
sunalpha = asin(tan(sundelta) / 0.434812); // вычисляем прямое восхождение солнца в радианах
float tS = _time + lambda — 180 + sunalpha * 360 / (2 * PI) — alpha; //вычисляем часовой угол звезды в градусах
height = 360 / 2 / PI * asin(sin(phi * 2 * PI / 360) * sin(delta * 2 * PI / 360) + cos(phi * 2 * PI / 360) * cos(delta * 2 * PI / 360) * cos(tS * 2 * PI / 360));//вычисляем высоту
az = 360 / 2 / PI * asin(sin(tS * 2 * PI / 360) * cos(delta * 2 * PI / 360) / cos(height * 2 * PI / 360)); //вычисляем астрономический азимут (с юга по часовой)
if (tS > 90 || tS < -90)
{
az = 180 — az;
}
// if (az > 180 && az < 270)
// az = az — 360;
}
void setup() {
Serial.begin(9600);
yaw.attach(10, 90);
pitch.attach(11, 180);
yaw.setSpeed(20);
yaw.setAccel(0.1);
pitch.setSpeed(20);
pitch.setAccel(0.1);
yaw.setAutoDetach(false);
pitch.setAutoDetach(false);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
but.tick();
yaw.tick();
pitch.tick();
if (but.isSingle())
{
counter += 1;
if (counter == 9)
counter = 0;
}
alpha = Coordinates[counter][0];
delta = Coordinates[counter][1];
GpsGetData();
CalculateParams();
digitalWrite(LED_BUILTIN, gps.location.isValid());
if (az > 90 && az < 180)
{
yaw.setTargetDeg(270 — az);
pitch.setTargetDeg(height);
}
if (az < -90 && az > -180)
{
yaw.setTargetDeg(-90 — az);
pitch.setTargetDeg(height);
}
if (az < 90 && az > -90)
{
yaw.setTargetDeg(90.0 — az);
pitch.setTargetDeg(180 — height);
}
}
Я не буду подробно разбирать каждую строчку, лишь остановлюсь на интересных моментах.
Урок астрономии
Про астротермины и еще кое-что
Если вы не знаете значение какого-либо термина, его можно узнать в википедии или спросить в комментариях – буду рад ответить.
Также, я не нашел в интернете алгоритма перевода координат из одной системы в другую.
… а момент здесь один – функция CalculateParams()
Что должна делать такая функция: принять на вход координаты звезды в экваториальной системе (прямое восхождение и склонение), время и координаты наблюдателя и выдать высоту и азимут объекта, т.е. по сути перевести координаты звезды из экваториальной системы (в которой звезды неподвижны) в горизонтальную (в которой звезды перемещаются в течение суток).
Реализовано это, используя формулы сферической тригонометрии, а также сферической астрономии в вакууме.
Алгоритм таков:
Вот как это реализовано:
void CalculateParams()
{
float _time = (h + (float)m / 60 + (float)s / 3600) * 360.0 / 24.0;//вычисляем время в часах
float sunlambda = (float)Days(d, mo, yr) * 360 / 365; // вычисляем эклиптическую долготы солнца в градусах
sundelta = asin(0.398749 * sin(sunlambda * 2 * PI / 360));//вычисляем склонение солнца в радианах
sunalpha = asin(tan(sundelta) / 0.434812); // вычисляем прямое восхождение солнца в радианах
float tS = _time + lambda — 180 + sunalpha * 360 / (2 * PI) — alpha; //вычисляем часовой угол звезды в градусах
height = 360 / 2 / PI * asin(sin(phi * 2 * PI / 360) * sin(delta * 2 * PI / 360) + cos(phi * 2 * PI / 360) * cos(delta * 2 * PI / 360) * cos(tS * 2 * PI / 360));//вычисляем высоту
az = 360 / 2 / PI * asin(sin(tS * 2 * PI / 360) * cos(delta * 2 * PI / 360) / cos(height * 2 * PI / 360)); //вычисляем астрономический азимут (с юга по часовой)
if (tS > 90 || tS < -90)
{
az = 180 — az;
}
}
Оценка погрешности
Оценим вклад каждого фактора в погрешность (отсортировано по вкладу в неточность)
также, я не учитываю уравнение времени) но погрешность из-за этого натекает небольшая.
Что еще можно сделать (todo)
существуют сервисы по «решению астрофото»: им загружаешь фотографию со звездами, а они вычисляют координаты центра кадра.
Что мы делаем:
На этом все, спасибо за внимание!
С радостью отвечу на ваши вопросы в комментариях.