Бортовой самописец на Arduino Mega

Мы решили покорить Космос.

Глобальные цели, достигаются малыми шагами. Поэтому первый рывок — покорение линии Армстронга. Мы готовимся запустить Arduino Uno в стратосферу, на высоту не менее 20 километров.

Чтобы не ограничиваться миганием светодиодами, мы собрали простой бортовой самописец. Arduino Mega получает текущие координаты, время и высоту от модуля GPS и записывает их через SD-ридер на карту памяти. Альтернативные данные о высоте и температуре мы получаем от IMU-барометра, а забортную температуру — с герметичного датчика DS18b20.

Искать шар мы будем по SMS-кам с координатами, которые будут отправляться раз в десять минут с помощью GPRS-шилда.

  • Платформы: Arduino Mega 2560
  • Языки программирования: Arduino (C++)
  • Тэги: GPS, GLONASS, стратосфера, ближний космос, бортовой самописец

Первое испытание

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

Один бортовой компьютер мы оставили в обычных «комнатных» условиях, а второй — на сутки поместили в контейнер с сухим льдом. Показания датчиков писались на SD-карточки и передавались между самописцами по RS-485. Кроме сенсоров мы протестировали аккумуляторы — Power Shield. Питание электроники осуществлялось от обычной розетки, а на аккумуляторах отслеживали саморазряд.

Пример кода

Для работы ниже приведённого скетча скачайте и установите библиотеки:

  1. Troyka-IMU — библиотека для работы с IMU-сенсором на 10 степеней свободы.
  2. TroykaGPS — библиотека для работы с модулем GPS/GLONASS
  3. AmperkaGPRS — библиотека для работы с GPRS Shield
flight-recorder-rs-485.ino
// библиотеки для работы с датчиком 18B20
#include <OneWire.h>
#include <DallasTemperature.h>
// библиотека для работы с аналоговым термометром (Troyka-модуль)
#include <TroykaThermometer.h>
// библиотека для работы с SPI
#include <SPI.h>
// библиотека для работы с SD-картами
#include <SD.h>
// библиотека для работы I²C
#include <Wire.h>
// библиотека для работы с модулями IMU
#include <TroykaIMU.h>
// библиотека для работы с GPS устройством
#include <TroykaGPS.h>
 
// 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");
  }
}

Результаты испытаний

Как собрать бортовой компьютер

Перед полётом в космос мы избавились от модулей RS-485 b аналоговых термометров. Перве свою задачу уже выполнили, вторые — не пригодятся вовсе. Наружную температуру мы будем измерять с помощью DS18b20, а температуру в гондоле узнаем от барометра.

Необходимые модули

Схема сборки

На Arduino Mega установим Troyka LP Sield и Troyka Mega Tail Shield

Для удобства подключения распаяем DS18B20 на Troyka Protoboard с резистором на 4.7 кОм. Теперь подключим Troyka модули и датчики с помощью трёхпроводных шлейфов.

Сверху установим GPRS Shield и переназначим управляющие пины передачи данных с помощью двух проводов мама-мама.

Готово! Осталось загрузить код прошивки в Arduino Mega.

Код прошивки

Для работы ниже приведённого скетча скачайте и установите библиотеки:

  1. Troyka-IMU — библиотека для работы с IMU-сенсором на 10 степеней свободы.
  2. TroykaGPS — библиотека для работы с модулем GPS/GLONASS
  3. AmperkaGPRS — библиотека для работы с GPRS Shield
flight-recorder.ino
// библиотеки для работы с датчиком 18B20
#include <OneWire.h>
#include <DallasTemperature.h>
// библиотека для работы с SPI
#include <SPI.h>
// библиотека для работы с SD-картами
#include <SD.h>
// библиотека для работы I²C
#include <Wire.h>
// библиотека для работы с модулями IMU
#include <TroykaIMU.h>
// библиотека для работы с GPS устройством
#include <TroykaGPS.h>
// библиотека для работы с GPRS устройством
#include <AmperkaGPRS.h>
 
// 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");
}

Полёт в стратосферу

Полный протокол полёта готовится к публикации.