Полётный контроллер для ракеты

Главной задачей простейшего полетного контроллера модели ракеты является запуск системы спасения (выброс парашюта). Вторая задача — запись полетных данных, в минимальном виде — запись значений высоты через определенные интервалы времени. Затем по этим данным можно будет рассчитать скорость и ускорение ракеты, а так же ответить на главный вопрос — на какую высоту взлетела ракета?

Обе эти задачи важны. Ракета без системы спасения становится одноразовой, поскольку обычно разрушается при приземлении. Но это еще полбеды. Свободно падающая ракета опасна для окружающих, поскольку, благодаря аэродинамической форме, может разогнаться до большой скорости и свалиться на чью-то дачу, теплицу, машину или, в худшем случае — на чью-то голову.

Полетные данные позволяют не только помериться, чья ракета взлетела выше, но и установить, соответствовал ли реальный полет расчетным данным, то есть был ли он успешным или нет. Без записи данных можно только установить сам факт полета ракеты.

Ракета, для которой делался этот контроллер, устроена следующим образом. В головной части размещен контроллер. За головной частью следует жестко прикрепленный к ней парашютный отсек, в котором последовательно размещены вышибной пиропатрон, искрогаситель, поршень и парашют. В парашютный отсек вставлен двигательный отсек, он удерживается за счет силы трения. Амортизационный фал соединяет между собой парашютный и двигательный отсеки, к нему же крепится парашют. После срабатывания пиропатрона давление образовавшихся газов толкает поршень, он выталкивает парашют и двигательный отсек. Ракета разделяется на две части, парашют раскрывается потом воздуха.

В моем варианте контроллер должен определить момент запуска, затем до приземления или до истечения 1 минуты (что наступит раньше) каждую 1/10 секунды записывать данные высоты на флеш-карту, засечь точку апогея, и на 10 метров ниже апогея поджечь пиропатрон выброса парашюта.

Главным достоинством этого контроллера я считаю простую в реализации и программировании схему. Все компоненты достаточно дешевы. Кроме того, он компактный и легко входит в корпус ракеты с внутренним диаметром 30 мм.

Недостатком так же является простота. Контроллер не имеет средств связи и определения координат, и не решает проблему поиска приземлившейся ракеты. Полетные данные можно получить только после извлечения флешки.

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

Контроллер построен на базе Arduino Pro Mini 3,3 V. Эта плата имеет вход питания RAW и может питаться от обычного LiPo аккумулятора 3,7 V. Плата очень компактная. Она не имеет USB-разъема, и для ее программирования нужен USB-UART переходник. Если переходника нет, но есть плата Arduino Uno, на которой процессор не припаян, а вставлен в слот, то процессор можно вынуть и плата станет переходником USB-UART.

Данные пишутся на micro SD карту, вставленную в разъем модуля (https://shop.robotclass.ru/index.php?route=product/product&path=121&product_id=1479). Решение самое простое, но не лучшее, так как от ускорений и вибрации карта может выпасть.

Вышибной пиропатрон парашюта поджигается через стандартный силовой ключ на транзисторе MOSFET. Питание на поджиг подается от отдельного аккумулятора, чтобы в случае короткого замыкания (мало ли что может случиться при зажигании?) не просело питание на контроллере. Пиропатрон является частью схемы, но описывать его конструкцию я не буду. Желающим рекомендую обратиться к отличному сайту о любительском ракетостроении kia-soft.narod.ru.

RGB светодиод сигнализирует о проблемах (если они возникнут) и о текущей стадии полета (по мнению контроллера). Такая сигнализация хорошо помогает при «наземной» отладке. У меня он подключен анодом к цифровому пину, на котором всегда HIGH. На схеме я для наглядности нарисовал «правильное» подключение, но в листинге программы под питание выделен отдельный пин.

Данные о высоте приходят с датчика BMP280. Для работы с датчиком используется библиотека Adafruit_BMP280.h.

Собрано все навесным монтажом и укреплено на куске пластика. Сначала такая идея казалась удачным вариантом, это позволяло менять расположение деталей. Но в процессе сборки я пришел к выводу, что лучше было бы спаять все на макетной плате, на двух сторонах для компактности. Дело в том, что надежно закрепить одну плату проще, чем четыре соединенных шлейфами компонента.

Параметры датчика BMP280

Руководств по работе с этим датчиком в Сети достаточно, здесь я бы хотел остановиться только на функции bmp.setSampling(), отвечающей, в том числе, за частоту измерений.

Параметры датчика, установленные по умолчанию:

bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, // Режим работы
Adafruit_BMP280::SAMPLING_X2, // Точность измерения температуры
Adafruit_BMP280::SAMPLING_X16, // Точность измерения давления
Adafruit_BMP280::FILTER_X16, // Уровень фильтрации
Adafruit_BMP280::STANDBY_MS_500); // Период пробужения, мСек

Первый параметр — режим работы датчика, имеет четыре варианта:
MODE_NORMAL – в данном режиме модуль циклически выходит из режима сна через установленный интервал времени. В активном состоянии он проводит измерения, сохраняет их в своей памяти и заново уходит в сон.
MODE_FORCED – в этом режиме датчик проводит измерения при получении команды от Arduino, после чего возвращается в состояние сна.
MODE_SLEEP – режим сна или пониженного энергопотребления.
MODE_SOFT_RESET_CODE – сброс на заводские настройки.

Второй и третий параметры — точность измерения температуры и давления, могут иметь следующие варианты:
SAMPLING_NONE — минимальная точность;
SAMPLING_X1 – точность АЦП 16 бит;
SAMPLING_X2 – точность АЦП 17 бит;
SAMPLING_X4 – точность АЦП 18 бит;
SAMPLING_X8 – точность АЦП 19 бит;
SAMPLING_X16 – точность АЦП 20 бит.

Четвертый параметр, уровень фильтрации, принимает следующие значения:
FILTER_OFF – фильтр выключен;
FILTER_X2;
FILTER_X4;
FILTER_X8;
FILTER_X16 – максимальный уровень фильтрации.

Наконец, последний параметр отвечает за частоту измерений и имеет следующие варианты:
STANDBY_MS_1 – модуль просыпается каждую миллисекунду;
STANDBY_MS_63 – модуль просыпается каждые 63 миллисекунды;
STANDBY_MS_125 – модуль просыпается каждых 125 миллисекунд;
STANDBY_MS_250 – модуль просыпается каждых 250 миллисекунд;
STANDBY_MS_500 – модуль просыпается каждых 500 миллисекунд;
STANDBY_MS_1000 – модуль просыпается каждую секунду;
STANDBY_MS_2000 – модуль просыпается каждые 2 секунды;
STANDBY_MS_4000 – модуль просыпается каждых 4 секунды.

Параметры по умолчанию меня в целом устраивают, но так как контроллер должен производить 10 замеров в секунду, последний параметр я установил STANDBY_MS_63.

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

Итак, единственным «органом чувств» для контроллера является датчик давления BMP280. На основе данных с датчика можно определить, находится ли ракета еще на земле или уже на земле, поднимается или снижается, пройдена ли точка апогея. Выводы делаются при сравнении текущей высоты с высотой старта и максимальной высотой полета (апогеем).

При включении питания контроллер подключается к датчику BMP280 и micro SD карте, отрывает файл лога, и если эти операции прошли успешно, переходит к основной программе. В случае неудачи сигнализирует светодиодом. Все это упаковано в функцию setup().

Работа основной части программы, записанной в функцию loop(), основана на смене стадий. Номер стадии хранится в переменной. При каждом повторе функции loop() программа обращается к этой переменной, чтобы определить стадию. Также при каждом повторе функции loop() производится замер высоты.

Стадии меняются последовательно.

Первая стадия — время до старта, начинается после выполнения функции setup(). Условие перехода ко второй стадии — текущая высота больше высоты старта на 10 метров.

Вторая стадия — полет до точки апогея. Высота максимума сравнивается с текущей, и если она меньше, то приравнивается к текущей. Условие перехода к третьей стадии — текущая высота меньше максимальной.

Третья стадия — свободное падение на 10 метров ниже апогея. Условие перехода к четвертой части — текущая высота меньше максимальной на 10 метров.

Четвертая стадия — спуск на парашюте. При наступлении этой стадии на 2 секунды подается питание на поджиг пиропатрона для выброса парашюта. Условие перехода к пятой стадии — текущая высота меньше чем высота старта + 10 метров.

Пятая стадия — ракета на земле. Продолжается до выключения или перезагрузки контроллера.

Высота полета записывается в файл в формате CSV каждые 100 мс во время выполнения стадий 2, 3 и 4.

Полет ракеты с этим контроллером прошел успешно. Анализ лога выявил ошибку в программе (в приложенном листинге она исправлена).

Исходный код

#include 
#include 
#include 
#include 

//////////
// пины //
#define redPin 2
#define greenPin 3
#define bluePin 4
#define firePin 6     // пин поджига
#define ledPin 7      // общий + на светодиод
#define CSPin 10      // пин SD карты

// переменные //
bool readyOK = 0;     // готовность
byte stage = 1;       // стадии полета
                      // 1 - стадия ожидания
                      // 2 - стадия активного полета
                      // 3 - стадия спуска
                      // 4 - стадия раскрытия парашюта
                      // 5 - стадия посадки
// высота //
float startAlt;       // высота старта
float maxAlt = 0;     // максимальная высота
float curAlt;         // текущая высота

// парашют //
unsigned long chuteTimer = 0; // таймер парашюта

// лог //
unsigned long logTimer = 0;   // таймер лога
unsigned long logTime = 0;    // таймер лога
int logCounter = 0;           // счетчик лога
int logLimit = 100;           // максимальное количество замеров
int logPeriod = 100;         // интервал между замерами
File logFile;         // файл лога
Adafruit_BMP280 bmp;  // I2C

void setup() {
   pinMode(redPin, OUTPUT);
   pinMode(greenPin, OUTPUT);
   pinMode(bluePin, OUTPUT);
   pinMode(firePin, OUTPUT);
   pinMode(ledPin, OUTPUT);

   digitalWrite(redPin, HIGH);       // светодиод красный погашен
   digitalWrite(greenPin, HIGH);     // светодиод зеленый погашен
   digitalWrite(bluePin, HIGH);      // светодиод синий погашен
   digitalWrite(ledPin, HIGH);       // общий пин

   //////////////////////////////
   // бессмысленая иллюминация //
   digitalWrite(redPin, LOW);        // светодиод красный горит
   delay(1000);
   digitalWrite(redPin, HIGH);       // светодиод красный погашен
   digitalWrite(greenPin, LOW);      // светодиод зеленый горит
   delay(1000);
   digitalWrite(greenPin, HIGH);     // светодиод зеленый погашен
   digitalWrite(bluePin, LOW);       // светодиод синий горит
   delay(1000);
   digitalWrite(bluePin, HIGH);      // светодиод синий погашен
   Serial.begin(9600);

   //////////////////////
   // заводим SD карту //
   if (!SD.begin(CSPin)) { 
     while (1) {
       // если не завелась, включаем сигнализацию
       for (int i = 0; i < 2; i++) {
         // мигаем два раза
         digitalWrite(redPin, LOW);
         delay(250);
         digitalWrite(redPin, HIGH);
         delay(250);
       }   
       delay(750);
     }
   }

   //////////////////////
   // заводим барометр //
   if (!bmp.begin()) {            // проверяем, завелся ли сенсор
     while (1){                        // если не завелся, включаем сигнализацию
       for (int i = 0; i < 3; i++) {   // мигаем три раза
         digitalWrite(redPin, LOW);
         delay(250);
         digitalWrite(redPin, HIGH);
         delay(250);
       }
       delay(750);
     }
   }

   //////////////////////////////
   // открываем файл на запись //
   logFile = SD.open("log.csv", FILE_WRITE); // пытаемся открыть файл
   if (!logFile){    
     while (1){                        // если не открылся, включаем сигнализацию
       for (int i = 0; i < 4; i++) {   // мигаем четыре раза
         digitalWrite(redPin, LOW);
         delay(250);
         digitalWrite(redPin, HIGH);
         delay(250);
       }
       delay(750);
     }
   }

   // если все завелось
   digitalWrite(redPin, HIGH);      // светодиод красный гасим
   digitalWrite(greenPin, LOW);     // светодиод зеленый горит
   readyOK = 1;                     // готовность

   /* Default settings from datasheet. */
   bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /* Operating Mode. */
                   Adafruit_BMP280::SAMPLING_X2,     /* Temp. oversampling */
                   Adafruit_BMP280::SAMPLING_X16,    /* Pressure oversampling */
                   Adafruit_BMP280::FILTER_X16,      /* Filtering. */
                   Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */
                   startAlt = bmp.readAltitude(1013.25); 

   /////////////////////////////
   // новые параметры датчика //
   bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /* Operating Mode. */
                   Adafruit_BMP280::SAMPLING_X2,     /* Temp. oversampling */
                   Adafruit_BMP280::SAMPLING_X16,    /* Pressure oversampling */
                   Adafruit_BMP280::FILTER_OFF,      /* Filtering. */
                   Adafruit_BMP280::STANDBY_MS_63);  /* Standby time. */
} // конец setup
 
void loop() {
  if (!readyOK){  // если ракета не в состоянии готовности
    return;       // ничего не делаем
  }

  ///////////////////
  // высота ракеты //
  curAlt = bmp.readAltitude(1013.25); // получаем текущую высоту
  if (maxAlt < curAlt){
    maxAlt = curAlt;                    // обновляем значение максимальной высоты
  }

  /////////////////////
  // выборс парашюта //
  if ((stage == 4) && (chuteTimer == 0)){   // если стадия 4 и таймер не запущен
    chuteTimer = millis();                  // запускаем таймер
    digitalWrite(firePin, HIGH);            // поджигаем запал
  }
  if ((millis() - chuteTimer) >= 2000){     // если жгём больше 2 сек
    digitalWrite(firePin, LOW);             // гасим запал
  }

  /////////////////////////////
  // запись в лог по таймеру //
  if (((stage == 2) || (stage == 3) || (stage == 4)) && ((millis() - logTimer) >= logPeriod)) {
     logTimer = millis();                
     logFile.print(logTimer / 1000);
     logFile.print(";");
     logFile.print(curAlt);
     logFile.println(";");
     logCounter++;
  }

  /////////////////////////////////
  // стадия 5 - окончание полета //
  if (stage == 5){
    logFile.print("Maximum Alt. = ");     // записываем значение максимальной высоты
    logFile.print(maxAlt);
    logFile.println(" m");
    logFile.println("");
    logTime = millis() - logTime;
    logFile.close();
    // закрываем файл
    while (1) {
      // мигаем синим и пищим
      if ((millis() - chuteTimer) >= 2000){
        // если жгём больше 2 сек
        digitalWrite(firePin, LOW);
        // гасим запал
      }
      digitalWrite(bluePin, HIGH);
      delay(500);
      digitalWrite(bluePin, LOW);
      delay(500);
    }
  }

  //////////////////////
  //// смена стадий ////

  //////////////////////
  // стадия ожидания //
  if (stage == 1) {
    /* stage = 2;   */
    if ((curAlt - startAlt) >= 10){
      // если ракета выше точки старта на 10 метров
      stage = 2;
      // меняем стадию на сталию активного полета
      logTime = millis();
      // начало времени записи лога
      digitalWrite(redPin, HIGH);
      digitalWrite(greenPin, HIGH);
      digitalWrite(bluePin, HIGH);
      digitalWrite(bluePin, LOW);
    }
  }
  // стадия активного полета //
  else if (stage == 2) {
    if (curAlt < maxAlt){             // если ракета ниже точки апогея
      stage = 3;                      // меняем сталию на стадию спуска
      digitalWrite(redPin, HIGH);
      digitalWrite(greenPin, HIGH);
      digitalWrite(bluePin, HIGH);
      digitalWrite(bluePin, LOW); 
    } 
  }
  // стадия спуска //
  else if (stage == 3) {
    if ((maxAlt - curAlt) >= 10){     // если ракета ниже точки апогея на 10 метров
      stage = 4;                      // меняем стадию на стадию спуска на парашюте
      digitalWrite(redPin, HIGH);
      digitalWrite(greenPin, HIGH);
      digitalWrite(bluePin, HIGH);
      digitalWrite(bluePin, LOW);
    }
  }
  // стадия раскрытия парашюта //
  else if (stage == 4) {
    if ((curAlt - startAlt) <= 10){     // если ракета спуслилась до 10 метров
      stage = 5;                      // меняем стадию на стадию приземления
    }
    digitalWrite(redPin, HIGH);
    digitalWrite(greenPin, HIGH);
    digitalWrite(bluePin, HIGH);
  }  
  // переполнение счетчика лога //
  if (logCounter >= logLimit){        // при переполнении счетчика лога (время работы  > 1 мин.)
    stage = 5;                        // меняем стадию на стадию приземления
  }
  // стадия посадки //
}


Изменено:

Полётный контроллер для ракеты: 8 комментариев

  1. (в приложенном листинге она исправлена)

    где можно скетч взять? или его нужно покупать?)

  2. Этот код работает вне зависимости от размеров, тяги и ДР параметров ?

  3. Откуда получается значение стартовой высоты? в коде он упоминается как переменная, но значения она не получает

  4. Можете, пожалуйста, добавить библиотеки, необходимые для программы? Их просто нет или я не вижу.

  5. while (1) {
    // мигаем синим и пищим
    if ((millis() — chuteTimer) >= 2000){
    // если жгём больше 2 сек
    digitalWrite(firePin, LOW);
    // гасим запал
    стадия 5-на земле,зачем поджигать запал???
    в коде явно присутствуют глюки…

  6. Привет, ребят. Может у кого есть схемка спасения ракеты только на 180 том барометре?

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.