Featured

Сквозь тернии к звездам: делаем устройство для наведения лазерной указки на любой небесный объект

Привет, Хабр!

Решил показать свою небольшую самоделку, которая работает примерно так:

Сквозь тернии к звездам: делаем устройство для наведения лазерной указки на любой небесный объект

Если КДПВ сделала свое дело — тогда добро пожаловать под кат 🙂

Небольшой спойлер
Я старался писать так, чтобы было максимально понятно всем

Аппаратная часть

Итак, для создания минимально работающего прототипа нам понадобятся:

  • Кусок доски или чего угодно, на чем можно закрепить все компоненты
  • Почти любой микроконтроллер. Я взял ардуино уно как самый простой вариант
  • GPS модуль (с него мы берем дату, время и координаты). Теоретически можно вместо него взять модуль часов реального времени, но тогда ваши координаты вам придется вводить вручную и «из коробки» устройство не заработает, но зато время холодного старта сильно сократится
  • Два сервопривода, или еще лучше два шаговых двигателя
  • Лазерная указка
  • Разная мелочь: паяльник, термоклей, макетная плата, провода, кнопка, конденсатор, ну и прямые руки.
  • По сборке особых хитростей нет: просто закрепить все компоненты на основании термоклеем.
    Только надо постараться чтобы оси сервоприводов и лазера были максимально перпендикулярны друг другу, это уменьшит погрешность наведения.

    Подключение
    Тут тоже ничего сложного:

    • GPS подключается по uart (понадобится только Rx, так как нам ничего не нужно отправлять на модуль
    • сервоприводы – в пины 10 (ось азимута) и 11 (ось высоты)
    • кнопка – во 2 пин
    • питание на все модули
    • опционально – конденсатор по питанию

    Фотографии моей реализации(19Мб)
    Сквозь тернии к звездам: делаем устройство для наведения лазерной указки на любой небесный объект

    Сквозь тернии к звездам: делаем устройство для наведения лазерной указки на любой небесный объект

    Сквозь тернии к звездам: делаем устройство для наведения лазерной указки на любой небесный объект

    Программная часть

    Переходим к самому интересному.

    Весь код можно условно разделить на три части:

  • Работа с 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!!!
  • На этом все, спасибо за внимание!

    С радостью отвечу на ваши вопросы в комментариях.

    Источник

    Zeen is a next generation WordPress theme. It’s powerful, beautifully designed and comes with everything you need to engage your visitors and increase conversions.

    Ещё
    Прически для овальной формы лица: ТОП-15 вариантов на лето