Мы решили покорить Космос.
Глобальные цели, достигаются малыми шагами. Поэтому первый рывок — покорение линии Армстронга. Мы готовимся запустить Arduino Uno в стратосферу, на высоту не менее 20 километров.
Чтобы не ограничиваться миганием светодиодами, мы собрали простой бортовой самописец. Arduino Mega получает текущие координаты, время и высоту от модуля GPS и записывает их через SD-ридер на карту памяти. Альтернативные данные о высоте и температуре мы получаем от IMU-барометра, а забортную температуру — с герметичного датчика DS18b20.
Искать шар мы будем по SMS-кам с координатами, которые будут отправляться раз в десять минут с помощью GPRS-шилда.
Высокие слои атмосферы не для неженок. Температуры до минус пятидесяти, сильная турбулентность, крайне низкое атмосферное давление и скачки влажности — всё это губительно для любительской электроники. Поэтому до запуска мы протестировали наши самописцы на Земле.
Один бортовой компьютер мы оставили в обычных «комнатных» условиях, а второй — на сутки поместили в контейнер с сухим льдом. Показания датчиков писались на SD-карточки и передавались между самописцами по RS-485. Кроме сенсоров мы протестировали аккумуляторы — Power Shield. Питание электроники осуществлялось от обычной розетки, а на аккумуляторах отслеживали саморазряд.
Для работы ниже приведённого скетча скачайте и установите библиотеки:
// библиотеки для работы с датчиком 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, а температуру в гондоле узнаем от барометра.
Для питания самописца мы использовали литий-ионные аккумуляторы Samsung INR18650-30Q.
На Arduino Mega установим Troyka LP Sield и Troyka Mega Tail Shield
Для удобства подключения распаяем DS18B20 на Troyka Protoboard с резистором на 4.7 кОм. Теперь подключим Troyka модули и датчики с помощью трёхпроводных шлейфов.
Сверху установим GPRS Shield и переназначим управляющие пины передачи данных с помощью двух проводов мама-мама.
Готово! Осталось загрузить код прошивки в Arduino Mega.
Для работы ниже приведённого скетча скачайте и установите библиотеки:
// библиотеки для работы с датчиком 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"); }
Полный протокол полёта готовится к публикации.