====== Бортовой самописец на Arduino Mega ======
Мы решили покорить Космос.
Глобальные цели, достигаются малыми шагами. Поэтому первый рывок — покорение линии Армстронга. Мы готовимся запустить Arduino Uno в стратосферу, на высоту не менее 20 километров. 
{{ :projects:flight-recorder:strato_1.jpg?nolink |}}
Чтобы не ограничиваться миганием светодиодами, мы собрали простой бортовой самописец. [[amp>product/arduino-mega-2560?utm_source=proj&utm_campaign=space-two&utm_medium=wiki|Arduino Mega]] получает текущие координаты, время и высоту от  [[amp>product/troyka-gps-glonass?utm_source=proj&utm_campaign=space-two&utm_medium=wiki|модуля GPS]] и записывает их через [[amp>product/troyka-sd?utm_source=proj&utm_campaign=space-two&utm_medium=wiki|SD-ридер]] на карту памяти. Альтернативные данные о высоте и температуре мы получаем от [[amp>product/troyka-barometer?utm_source=proj&utm_campaign=space-two&utm_medium=wiki|IMU-барометра]], а забортную температуру — с [[amp>product/sealed-temperature-sensor-ds18b20?utm_source=proj&utm_campaign=space-two&utm_medium=wiki|герметичного датчика DS18b20]].
{{ :projects:flight-recorder:flight-recorder.jpg?nolink |}}
Искать шар мы будем по SMS-кам с координатами, которые будут отправляться раз в десять минут с помощью [[amp>product/arduino-gprs-shield?utm_source=proj&utm_campaign=space-two&utm_medium=wiki|GPRS-шилда]].
  * Платформы: Arduino Mega 2560
  * Языки программирования: Arduino (C++)
  * Тэги: GPS, GLONASS, стратосфера, ближний космос, бортовой самописец
===== Первое испытание =====
Высокие слои атмосферы не для неженок. Температуры до минус пятидесяти, сильная турбулентность, крайне низкое атмосферное давление и скачки влажности — всё это губительно для любительской электроники. Поэтому до запуска мы протестировали наши самописцы на Земле. 
{{youtube>-Ad01l9mrUY?large}}
Один бортовой компьютер мы оставили в обычных «комнатных» условиях, а второй — на сутки поместили в контейнер с сухим льдом. Показания датчиков писались на SD-карточки и передавались между самописцами по RS-485. Кроме сенсоров мы протестировали аккумуляторы — Power Shield. Питание электроники осуществлялось от обычной розетки, а на аккумуляторах отслеживали саморазряд.
==== Пример кода ====
Для работы ниже приведённого скетча скачайте и установите библиотеки:
  - {{:projects:flight-recorder:onewire.zip|OneWire}} и {{:projects:flight-recorder:dallastemperature.zip|DallasTemperature}} для работы с [[amp>product/sealed-temperature-sensor-ds18b20?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|датчиком температуры DS18B20]]
  - [[https://github.com/amperka/TroykaThermometer|TroykaThermometer]] — библиотека для работы с [[amp>product/troyka-temperature-sensor?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|с аналоговым термометром (Troyka-модуль)]]
  - [[https://github.com/amperka/Troyka-IMU|Troyka-IMU]] — библиотека для работы с [[amp>product/troyka-imu-10-dof?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|IMU-сенсором на 10 степеней свободы]].
  - [[https://github.com/amperka/TroykaGPS|TroykaGPS]] — библиотека для работы с модулем [[amp>product/troyka-gps-glonass-extended-receiver?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|GPS/GLONASS]]
  - {{:projects:flight-recorder:amperkagprs.zip|AmperkaGPRS}} — библиотека для работы с [[amp>product/arduino-gprs-shield?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|GPRS Shield]]
// библиотеки для работы с датчиком 18B20
#include 
#include 
// библиотека для работы с аналоговым термометром (Troyka-модуль)
#include 
// библиотека для работы с SPI
#include 
// библиотека для работы с SD-картами
#include 
// библиотека для работы I²C
#include 
// библиотека для работы с модулями IMU
#include 
// библиотека для работы с GPS устройством
#include 
// serial-порт к которому подключён GPS-модуль
#define GPS_SERIAL        Serial1
// serial-порт к которому подключён RS485
#define RS485_SERIAL      Serial2
#define RS485_PIN_MASTER  22
// пин подключения CS microSD-карты
#define SD_CS_PIN  A5
// пин подключения State Volt
#define VOLT_PIN A0
// пин подключения датчика 18B20
#define TEMP_18B20_PIN A1
// пин подключения датчика TMP36
#define TEMP_TMP36_PIN A2
// задаём размер массива для времени
#define MAX_SIZE_MASS 16
// интервал времени записи данных на карту
#define INTERVAL          10000
// создадаём объект для работы с библиотекой OneWire
OneWire oneWire(TEMP_18B20_PIN);
// создадаём объект для работы с библиотекой DallasTemperature
DallasTemperature sensor18B20(&oneWire);
// создаём объект для работы с аналоговым термометром
// и передаём ему номер пина выходного сигнала
TroykaThermometer sensorTMP36(TEMP_TMP36_PIN);
// создаём объект для работы с барометром
Barometer barometer;
// создаём объект класса GPS и передаём в него объект Serial1 
GPS gps(GPS_SERIAL);
// массив для хранения текущего времени
char strTime[MAX_SIZE_MASS];
// массив для хранения текущей даты
char strDate[MAX_SIZE_MASS];
// запоминаем текущее время
long startMillis = millis();
// данные модулей для записи
String dataString = "";
String dataStringRS485 = "";
float temperature18B20;
float temperatureTMP36;
float temperatureBarometer;
float pressureBarometer;
float altitudeBarometer;
String timeGPS;
String dateGPS;
String altitudeGPS;
float voltPowerShield;
int i = 0;
void setup()
{
  // открываем последовательный порт для мониторинга действий в программе
  Serial.begin(115200);
  // выводим сообщение в Serial-порт о поиске карты памяти
  Serial.println("Initializing SD card...");
  // если microSD-карта не была обнаружена
  if (!SD.begin(SD_CS_PIN)) {
    // выводим сообщение об ошибке
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  } else {
    Serial.println("Card initialized");
  }
  // начало работы с датчиком DS18B20
  sensor18B20.begin();
  // установим разрешение датчика
  sensor18B20.setResolution(12);
  // выводим сообщение об удачной инициализации
  Serial.println("18B20 is OK");
  // выводим сообщение об удачной инициализации
  Serial.println("TMP36 is OK");
  // инициализация барометра
  barometer.begin();
  // выводим сообщение об удачной инициализации
  Serial.println("LPS331 is OK");
  // открываем Serial-соединение с GPS-модулем
  GPS_SERIAL.begin(115200);
  // выводим сообщение об удачной инициализации
  Serial.println("GPS is OK");
  // открываем Serial-соединение с GPS-модулем
  RS485_SERIAL.begin(115200);
  // подаём высокий уровень на пин направление
  // значит устройство будет передавать данные
  pinMode(RS485_PIN_MASTER, OUTPUT);
  digitalWrite(RS485_PIN_MASTER, LOW);
  // выводим сообщение об удачной инициализации
  Serial.println("GPS is OK");
}
 
void loop()
{
  getTemperature18B20();
  getTemperatureTMP36();
  getDataBarometer();
  getDataGPS();
  getVoltState();
  dataString = dateGPS;
  dataString += "\t";
  dataString += timeGPS;
  dataString += "\t";
  dataString += temperatureTMP36;
  dataString += "\t";
  dataString += temperature18B20;
  dataString += "\t";
  dataString += temperatureBarometer;
  dataString += "\t";
  dataString += pressureBarometer;
  dataString += "\t";
  dataString += altitudeBarometer;
  dataString += "\t";
  dataString += altitudeGPS;
  dataString += "\t";
  dataString += voltPowerShield;
  dataString += "\t";
  if (millis() - startMillis > INTERVAL) {
    readDataRS485();
    dataString += "|\t";
    dataString +=  dataStringRS485;
    // сохраняем данные с модулей на карту памяти
    saveSD();
    // запоминаем текущее время
    startMillis = millis();
  }
}
void readDataRS485() {
  dataStringRS485 = "";
  RS485_SERIAL.flush();
  // если появились данные с модуля RS-485
  if (RS485_SERIAL.available() > 0) {
    while (RS485_SERIAL.available() > 0) {
      // считываем данные
      char c =  RS485_SERIAL.read();
      dataStringRS485 += c;
    }
  } else {
    dataStringRS485 = "No Data from RS485";
  }
}
void getVoltState() {
  int sensorValue = analogRead(VOLT_PIN);
  voltPowerShield = sensorValue * (5.0 / 1023.0);
}
void getDataGPS() {
  String timeGPSString;
  String dateGPSString;
  // если пришли данные с GPS-модуля
  if (gps.available()) {
    // считываем данные и парсим
    gps.readParsing();
    // проверяем состояние GPS-модуля
    switch(gps.getState()) {
      // всё OK
      case GPS_OK:
        gps.getTime(strTime, MAX_SIZE_MASS);
        gps.getDate(strDate, MAX_SIZE_MASS);
        timeGPSString = (String)strTime;
        timeGPS = timeGPSString;
        dateGPSString = (String)strDate;
        dateGPS = dateGPSString;
        altitudeGPS = gps.getAltitude();
        break;
      // ошибка данных
      case GPS_ERROR_DATA:
        //Serial.println("GPS error data");
        dateGPS = "Error";
        timeGPS = "Error";
        altitudeGPS = "Error";
        break;
      // нет соединение со спутниками
      case GPS_ERROR_SAT:
        //Serial.println("GPS no connect to satellites!!!");
        dateGPS = "No Sat";
        timeGPS = "No Sat";
        altitudeGPS = "No Sat";
        break;
    }
  }
}
void getDataBarometer() {
  temperatureBarometer = barometer.readTemperatureC();
  pressureBarometer  = barometer.readPressureMillibars();
  altitudeBarometer = barometer.pressureToAltitudeMeters(pressureBarometer);
}
void getTemperature18B20() {
  // считываем данные с цифрового термометра 18B20
  sensor18B20.requestTemperatures();
  temperature18B20 =  sensor18B20.getTempCByIndex(0);
}
void getTemperatureTMP36() {
  // считываем данные с аналогового термометра TMP36
  sensorTMP36.read();
  temperatureTMP36 = sensorTMP36.getTemperatureC();
}
void saveSD() {
  // создаём файл для записи
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  // если файл доступен для записи
  if (dataFile) {
    // сохраняем данные
    dataFile.println(dataString);
    Serial.print(dataString);
    // закрываем файл
    dataFile.close();
    // выводим сообщение об удачной записи
    Serial.println("Save OK");
  } else {
    // если файл не доступен
    Serial.println("Error opening datalog.txt");
  }
}
==== Результаты испытаний ====
  * [[https://drive.google.com/open?id=1hAQsNHcrC2s-ccSjlizzCJf2fcxRYFv7|«сырой» лог-файл]]
  * [[https://drive.google.com/file/d/11CmFhwR33WH45SD6wv7WinlizCJaR6hn/view?usp=sharing|протокол в формате Excel]]
===== Как собрать бортовой компьютер =====
{{ :projects:flight-recorder:arduino-in-space-3.jpg?nolink |}}
Перед полётом в космос мы избавились от модулей RS-485 b аналоговых термометров. Перве свою задачу уже выполнили, вторые — не пригодятся вовсе. Наружную температуру мы будем измерять с помощью DS18b20, а температуру в гондоле узнаем от барометра.
==== Необходимые модули ====
  * [[amp>product/arduino-mega-2560?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|Arduino Mega 2560]]
  * [[amp>product/troyka-gps-glonass-extended-receiver?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|GPS приёмник с выносной антенной]]
  * [[amp>product/arduino-gprs-shield?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|GPRS шилд]]
  * [[amp>product/troyka-barometer?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|Барометр LPS331AP]]
  * [[amp>product/sealed-temperature-sensor-ds18b20?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|Герметичный датчик температуры DS18B20]]
  * [[amp>product/arduino-troyka-shield-lp?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|Плата расширения Troyka Shield LP]]
  * [[amp>product/arduino-troyka-mega-tail-shield?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|Плата расширения для дополнительных пинов Arduino Mega]]
Для питания самописца мы использовали литий-ионные аккумуляторы Samsung INR18650-30Q.
==== Схема сборки ====
На Arduino Mega установим Troyka LP Sield и Troyka Mega Tail Shield
{{ :projects:flight-recorder:11.png?nolink |}}
Для удобства подключения распаяем DS18B20 на Troyka Protoboard с резистором на 4.7 кОм.
Теперь подключим Troyka модули и датчики с помощью трёхпроводных шлейфов.
{{ :projects:flight-recorder:2.png?nolink |}}
Сверху установим GPRS Shield и переназначим управляющие пины передачи данных с помощью двух проводов мама-мама.
{{ :projects:flight-recorder:3.png?nolink |}}
Готово! Осталось загрузить код прошивки в Arduino Mega.
==== Код прошивки ====
Для работы ниже приведённого скетча скачайте и установите библиотеки:
  - {{:projects:flight-recorder:onewire.zip|OneWire}} и {{:projects:flight-recorder:dallastemperature.zip|DallasTemperature}} для работы с [[amp>product/sealed-temperature-sensor-ds18b20?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|датчиком температуры DS18B20]]
  - [[https://github.com/amperka/Troyka-IMU|Troyka-IMU]] — библиотека для работы с [[amp>product/troyka-imu-10-dof?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|IMU-сенсором на 10 степеней свободы]].
  - [[https://github.com/amperka/TroykaGPS|TroykaGPS]] — библиотека для работы с модулем [[amp>product/troyka-gps-glonass-extended-receiver?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|GPS/GLONASS]]
  - {{:projects:flight-recorder:amperkagprs.zip|AmperkaGPRS}} — библиотека для работы с [[amp>product/arduino-gprs-shield?utm_source=proj&utm_campaign=space-four&utm_medium=wiki|GPRS Shield]]
// библиотеки для работы с датчиком 18B20
#include 
#include 
// библиотека для работы с SPI
#include 
// библиотека для работы с SD-картами
#include 
// библиотека для работы I²C
#include 
// библиотека для работы с модулями IMU
#include 
// библиотека для работы с GPS устройством
#include 
// библиотека для работы с GPRS устройством
#include 
// serial-порт к которому подключён GPS-модуль
#define GPS_SERIAL        Serial1
// serial-порт к которому подключён GPRS-модуль
#define GPRS_SERIAL       Serial3
// пин подключения CS microSD-карты
#define SD_CS_PIN         10
// пин подключения датчика 18B20
#define TEMP_18B20_PIN    A1
// задаём размер массива для времени
#define MAX_SIZE_MASS     16
// интервал времени записи данных на SD-карту
#define INTERVAL_SD       1000
// интервал времени передачи данных через СМС
#define INTERVAL_SMS      600000
// номер на который будем отправлять сообщение
#define PHONE_NUMBER  "+74959379992"
// создадаём объект для работы с библиотекой OneWire
OneWire oneWire(TEMP_18B20_PIN);
// создадаём объект для работы с библиотекой DallasTemperature
DallasTemperature sensor18B20(&oneWire);
// создаём объект для работы с барометром
Barometer barometer;
// создаём объект класса GPS и передаём в него объект Serial1 
GPS gps(GPS_SERIAL);
// создаём объект класса GPRS и передаём в него объект Serial1 
GPRS gprs(GPRS_SERIAL);
// можно указать дополнительные параметры — пины PK и ST
// по умолчанию: PK = 2, ST = 3
// GPRS gprs(GPRS_SERIAL, 2, 3);
// массив для хранения текущего времени
char strTime[MAX_SIZE_MASS];
// массив для хранения текущей даты
char strDate[MAX_SIZE_MASS];
// массив для хранения широты в градусах, минутах и секундах
char latitudeBase60[MAX_SIZE_MASS];
// массив для хранения долготы в градусах, минутах и секундах
char longitudeBase60[MAX_SIZE_MASS];
// запоминаем текущее время
long startMillisSD = millis();
// запоминаем текущее время
long startMillisSMS = millis();
// данные модулей для записи на карту SD
String dataStringSD = "";
// данные модулей для отправки СМС
String dataStringSMS = "";
// переменные для хранения данных с датчиков и модулей
float temperature18B20;
float temperatureBarometer;
float pressureBarometer;
float altitudeBarometer;
String timeGPS;
String dateGPS;
String altitudeGPS;
String latitudeBase60GPS;
String longitudeBase60GPS;
void setup()
{
  // открываем последовательный порт для мониторинга действий в программе
  Serial.begin(115200);
  // выводим сообщение в Serial-порт о поиске карты памяти
  Serial.println("Initializing SD card...");
  // если microSD-карта не была обнаружена
  if (!SD.begin(SD_CS_PIN)) {
    // выводим сообщение об ошибке
    Serial.println("Card failed, or not present");
  } else {
    Serial.println("Card initialized");
  }
  // начало работы с датчиком DS18B20
  sensor18B20.begin();
  // установим разрешение датчика
  sensor18B20.setResolution(12);
  // выводим сообщение об удачной инициализации
  Serial.println("18B20 is OK");
  // инициализация барометра
  barometer.begin();
  // выводим сообщение об удачной инициализации
  Serial.println("LPS331 is OK");
  // открываем Serial-соединение с GPS-модулем
  GPS_SERIAL.begin(115200);
  // выводим сообщение об удачной инициализации
  Serial.println("GPS is OK");
  // открываем Serial-соединение с GPRS Shield
  GPRS_SERIAL.begin(115200);
  // включаем GPRS-шилд
  gprs.powerOn();
  delay(1000);
  if (!gprs.begin()) {
    // если связи нет, ждём 1 секунду
    // и выводим сообщение об ошибке;
    Serial.print("GPRS Begin error\r\n");
  }
  // вывод об удачной инициализации GPRS Shield
  Serial.println("GPRS is OK");
}
 
void loop()
{
  // считываем данные с модулей и датчиков
  getTemperature18B20();
  getDataBarometer();
  getDataGPS();
  // собираем пакет данных для записи на SD-карту
  dataStringSD = dateGPS;
  dataStringSD += "\t";
  dataStringSD += timeGPS;
  dataStringSD += "\t";
  dataStringSD += latitudeBase60GPS;
  dataStringSD += "\t";
  dataStringSD += longitudeBase60GPS;
  dataStringSD += "\t";
  dataStringSD += temperature18B20;
  dataStringSD += "\t";
  dataStringSD += temperatureBarometer;
  dataStringSD += "\t";
  dataStringSD += pressureBarometer;
  dataStringSD += "\t";
  dataStringSD += altitudeBarometer;
  dataStringSD += "\t";
  dataStringSD += altitudeGPS;
  dataStringSD += "\t";
  
  // собираем пакет данных для СМС
  dataStringSMS = dateGPS;
  dataStringSMS += " ";
  dataStringSMS += timeGPS;
  dataStringSMS += " ";
  dataStringSMS += latitudeBase60GPS;
  dataStringSMS += " ";
  dataStringSMS += longitudeBase60GPS;
  dataStringSMS += " ";
  dataStringSMS += temperature18B20;
  dataStringSMS += " ";
  dataStringSMS += temperatureBarometer;
  dataStringSMS += " ";
  dataStringSMS += pressureBarometer;
  dataStringSMS += " ";
  dataStringSMS += altitudeBarometer;
  dataStringSMS += " ";
  dataStringSMS += altitudeGPS;
  dataStringSMS += " ";
  
  if (millis() - startMillisSD > INTERVAL_SD) {
    // сохраняем данные с модулей на карту памяти
    saveSD();
    // запоминаем текущее время
    startMillisSD = millis();
  }
  if (millis() - startMillisSMS > INTERVAL_SMS) {
    // отправляем данные СМС сообщениям
    sendSMS();
    // запоминаем текущее время
    startMillisSMS = millis();
  }
}
// функция считывания данных с GPS-модуля в глобальные переменные
void getDataGPS() {
  // если пришли данные с GPS-модуля
  if (gps.available()) {
    // считываем данные и парсим
    gps.readParsing();
    // проверяем состояние GPS-модуля
    switch(gps.getState()) {
      // всё OK
      case GPS_OK:
        gps.getTime(strTime, MAX_SIZE_MASS);
        gps.getDate(strDate, MAX_SIZE_MASS);
        gps.getLatitudeBase60(latitudeBase60, MAX_SIZE_MASS);
        gps.getLongitudeBase60(longitudeBase60, MAX_SIZE_MASS);
        timeGPS = (String)strDate;
        dateGPS = (String)strDate;
        latitudeBase60GPS = (String)latitudeBase60;
        longitudeBase60GPS = (String)longitudeBase60;
        altitudeGPS = gps.getAltitude();
        break;
      // ошибка данных
      case GPS_ERROR_DATA:
        dateGPS = "Error";
        timeGPS = "Error";
        latitudeBase60GPS = "Error";
        longitudeBase60GPS = "Error";
        altitudeGPS = "Error";
        break;
      // нет соединение со спутниками
      case GPS_ERROR_SAT:
        dateGPS = "No Sat";
        timeGPS = "No Sat";
        latitudeBase60GPS = "No Sat";
        longitudeBase60GPS = "No Sat";
        altitudeGPS = "No Sat";
        break;
    }
  }
}
// функция считывания данных с барометра в глобальные переменные
void getDataBarometer() {
  temperatureBarometer = barometer.readTemperatureC();
  pressureBarometer  = barometer.readPressureMillibars();
  altitudeBarometer = barometer.pressureToAltitudeMeters(pressureBarometer);
}
// функция считывания данных с датчика 18B20 в глобальную переменную
void getTemperature18B20() {
  // считываем данные с цифрового термометра 18B20
  sensor18B20.requestTemperatures();
  temperature18B20 =  sensor18B20.getTempCByIndex(0);
}
// функция записи данных с модулей на SD-карту
void saveSD() {
  // создаём файл для записи
  File dataFile = SD.open("datalog.txt", FILE_WRITE);
  // если файл доступен для записи
  if (dataFile) {
    // сохраняем данные
    dataFile.println(dataStringSD);
    Serial.println(dataStringSD);
    // закрываем файл
    dataFile.close();
    // выводим сообщение об удачной записи
    Serial.println("Save OK");
  } else {
    // если файл не доступен
    // выводим сообщение об ошибке
    Serial.println("Error opening file");
  }
}
// функция отправки данные с модулей СМС сообщением
void sendSMS() {
  char dataSMS[128] = "";
  dataStringSMS.toCharArray(dataSMS, 128);
  gprs.sendSMS(PHONE_NUMBER, dataSMS);
  Serial.println("SMS sent OK");
}
===== Полёт в стратосферу =====
{{youtube>XO4KWPQMSdc?large}}
Полный протокол полёта готовится к публикации.
{{ :projects:flight-recorder:profile.jpg?nolink |}}
  * [[https://drive.google.com/file/d/1lYXRzHtBSLnoobYmI3PJF8WH-Xnp87wp/view?usp=sharing|«Сырые» данные]] первого самописца
  * [[https://drive.google.com/file/d/1rhYWjvbBGs-tBB2T0lEDOg3TufGVJrkL/view?usp=sharing|«Сырые» данные]] второго самописца