Решил показать свою небольшую самоделку, которая работает примерно так:
Если КДПВ сделала свое дело тогда добро пожаловать под кат :)
Аппаратная часть
Итак, для создания минимально работающего прототипа нам понадобятся:
- Кусок доски или чего угодно, на чем можно закрепить все компоненты
- Почти любой микроконтроллер. Я взял ардуино уно как самый простой вариант
- GPS модуль (с него мы берем дату, время и координаты). Теоретически можно вместо него взять модуль часов реального времени, но тогда ваши координаты вам придется вводить вручную и из коробки устройство не заработает, но зато время холодного старта сильно сократится
- Два сервопривода, или еще лучше два шаговых двигателя
- Лазерная указка
- Разная мелочь: паяльник, термоклей, макетная плата, провода, кнопка, конденсатор, ну и прямые руки.
По сборке особых хитростей нет: просто закрепить все компоненты на основании термоклеем.
Только надо постараться чтобы оси сервоприводов и лазера были максимально перпендикулярны друг другу, это уменьшит погрешность наведения.
Подключение
Тут тоже ничего сложного:
- GPS подключается по uart (понадобится только Rx, так как нам ничего не нужно отправлять на модуль
- сервоприводы в пины 10 (ось азимута) и 11 (ось высоты)
- кнопка во 2 пин
- питание на все модули
- опционально конденсатор по питанию
Программная часть
Переходим к самому интересному.
Весь код можно условно разделить на три части:
- Работа с GPS, кнопкой и сервами
- Работа с астрономией
- Главный цикл программы
Заметки по коду:
Для жпс использована библиотека 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)
- Сменить сервы на шаговые двигатели с их микрошагами;
- Немного усовершенствовать алгоритм
- Есть метод полного устранения погрешности из-за кривого
горизонта и азимута:
существуют сервисы по решению астрофото: им загружаешь фотографию со звездами, а они вычисляют координаты центра кадра.
Что мы делаем:- прикрепляем к большому телескопу маленький телескопчик с камерой
- поворачиваем шаговики на 3 случайных точки на небе, делаем фотографии
- отправляем их на один из сервисов, используя его API и получаем координаты точек
- сложными программными методами избавляемся от погрешности установки основного телескопа
- PROFIT!!!
На этом все, спасибо за внимание!
С радостью отвечу на ваши вопросы в комментариях.