Датчик положения GY-88 + SD-кардридер + Arduino = Полётный компьютер

Купив по дешёвке несколько датчиков для Arduino, я стал придумывать куда их приспособить, и решил создать бортовой компьютер для водяной ракеты, который бы измерял параметры полёта, такие как: ускорение, скорость, высота подъёма, и мог бы в верхней точке траектории дать команду на выпуск парашюта. Также, желательно было иметь возможность после посадки ракеты прочитать «чёрный ящик» и узнать, с каким ускорением она взлетала, как быстро набрала максимальную скорость, и какой была высота взлёта. Если же парашют не раскроется — чёрный ящик помог бы узнать, с какой скоростью ракета врезается в землю 🙂

Контроллер

В качестве вычислительного устройства я выбрал Arduino UNO и Arduino Nano. Они оба обладают одинаковой вычислительной мощность, объёмом памяти, и количеством контактов. По сути, это одинаковые устройства, разница только в размере:

За счёт того, что начинка у обоих Arduino одинаковая, можно отлаживать схему и код на большой и удобной Arduino UNO, а, когда всё будет готово, собрать готовое устройство на Arduino Nano.

Датчик положения

Чтобы летающий бортовой компьютер мог понимать что с ним происходит, ему нужен гироскоп для измерения угловых скоростей, акселерометр для измерения линейных ускорений, барометр, для вычисления высоты, а также, трёхмерный компас для определения направления на север. Зачем покупать все эти датчики по отдельности, если можно купить один датчик, соединяющий в себе эти функции? Как раз таким является GY-88, который, по сути, состоит из датчиков ускорения, угловых скоростей, давления и магнитного поля, установленным на небольшой плате. Выглядит это так:

Регистрация данных

Для последующего за запуском анализа полетных данных на земле понадобится устройство хранения информации. Поскольку используемые контроллеры Arduino на базе ATmega328 и ATmega328P имеют всего 1024 байта энергонезависимой памяти, её нам явно не хватит. К счастью, можно без проблем приобрести кардридер, подключаемый к Arduino, и карту памяти, на которую будет вестись запись. Мало того, на самом деле, можно подключить карту памяти SD или MicroSD напрямую к ардуино, однако это будет несколько сложнее, так что воспользуемся дополнительным устройством, имеющим, кроме прочего, удобный разъём для вставки карты MicroSD.

Питание

Поскольку длинный кабель питания тянуть к ракете — не вариант, понадобится источник питания, который полетит вместе с ней. Для этого подойдёт любая батарея на 5 и более вольт. Хотя можно было взять 4 пальчиковые батарейки, я выбрал одну «крону», на 9 вольт, которая при таком напряжении обладает гораздо меньше массой, чем 3-4 пальчиковых батарейки. Встроенный в Arduino стабилизатор напряжения самостоятельно сможет снизить входные 9 вольт до необходимых ему пяти.
Для подключения, разумеется, понадобились клеммы:

Подключение GY-88

Для начала нужно убедиться что GY-88 работает. Для этого, во-первых, нужно правильно его подключить к Arduino, а, во-вторых, написать кусочек кода, который будет отвечать за чтение данных с датчиков, и отправку их на ПК (для контроля).
Мне достался Gy-88 среди входов которого был подписанный «3V3», что означает «вход на 3.3 вольта». Поскольку в других инструкциях по подключению такой вход не встречается, я нарисовал схему для своего случая:

Если у вас нет контакта «3V3», то у вас будет только 4 провода вместо 5.

Проверка работоспособности GY-88

1. Нужно собрать на макетной плате нарисованную выше схему

2. В редакторе кода Arduino IDE нужно установить библиотеку Wire:

2.1 Открываем менеджер библиотек:

2.2 Находим Wire и устанавливаем:

3. Создать скетч, и скопировать в него следующий код (взять его можно также по адресу: https://github.com/MihanEntalpo/flight-computer-app/blob/master/gy-88.ino):

 
#include <Wire.h>
#include "BMP085.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "HMC5883L.h"
 
HMC5883L compass;
BMP085 pressure_m;
MPU6050 accelgyro;
 
int16_t ax, ay, az;
int16_t gx, gy, gz;
#define LED_PIN 13
bool blinkState = false;
double dt = 0;
long counter = 0;
double t0 = -1;
double millis_t = 0;
 
void setup(){
    //Включаем последовательный порт на максимальную скорость
    Serial.begin(230400);
    //Включаем протокол Wire
    Wire.begin();
    TWBR = 24;
    Serial.println("Initializing I2C devices...");    
    //инициализируем акселерометр с гироскопом
    accelgyro.initialize();
    accelgyro.setMasterClockSpeed(13);
    accelgyro.setI2CMasterModeEnabled(true);
    //инициализируем компас
    compass = HMC5883L();
    setupHMC5883L(); 
 
    //проверяем содеинение
    Serial.println("Testing device connections...");
    Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
 
    //калибруем датчик давления
    pressure_m.bmp085Calibration();
}
 
void loop()
{
  millis_t = millis();
  //Считаем температуру (её знает датчик давления)
  float temperature = pressure_m.bmp085GetTemperature(); //MUST be called first
  //Считаем давление
  float pressure = pressure_m.bmp085GetPressure();
  //Вычислим высоту над уровнем моря
  float altitude = pressure_m.calcAltitude(pressure);
 
  //Вычислим dt (интервал между замерами в миллисекундах)
  if (t0 < 0)
  {
    t0 = millis_t;
  }
  else
  {
    dt = millis_t - t0;
    t0 = millis_t;
  }
 
  //Интервал между замерами
  Serial.print("dt:"); Serial.print(dt / 1000, 3);
  //Сколько времени прошло с запуска?
  Serial.print(" tm:"); Serial.print(millis_t);
  //Температура, в градусах Цельсия
  Serial.print(" t:"); Serial.print(temperature, 2); 
  //Давление, в паскалях
  Serial.print(" p:"); Serial.print(pressure, 0); 
  //Высота над уровнем моря, в метрах
  Serial.print(" alt:"); Serial.print(altitude, 2); 
 
  //Считаем значения с магнитометра
  MagnetometerScaled scaled = compass.ReadScaledAxis();
  //Считаем значения с акселерометра и гироскопа
  accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
 
  //Данные акселерометра:
  Serial.print(" ax:"); Serial.print(ax);
  Serial.print(" ay:"); Serial.print(ay);
  Serial.print(" az:"); Serial.print(az);
  //Данные гироскопа
  Serial.print(" wx:"); Serial.print(gx);
  Serial.print(" wy:"); Serial.print(gy);
  Serial.print(" wz:"); Serial.print(gz);
  //Данные магнитометра    
  Serial.print(" cx:"); Serial.print(scaled.XAxis, 3);
  Serial.print(" cy:"); Serial.print(scaled.YAxis, 3);
  Serial.print(" cz:"); Serial.print(scaled.ZAxis, 3);
  Serial.println();
}
 
//Настройка модуля HMC5883L (компаса)
void setupHMC5883L(){
  //Setup the HMC5883L, and check for errors
  int error;  
  error = compass.SetScale(1.3); //Set the scale of the compass.
  if(error != 0) Serial.println(compass.GetErrorText(error)); //check if there is an error, and print if so
 
  error = compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous
  if(error != 0) Serial.println(compass.GetErrorText(error)); //check if there is an error, and print if so
}

4. Подключить Arduino к ПК
5. Загрузить скетч на Arduino
6. Открыть монитор порта и выбрать для него правильную скорость порта (230400, как в коде)
7. Внимательно посмотреть на выдачу данных, и убедиться что там что-то вроде этого:

dt:0.040 tm:3419.00 t:30.20 p:100612 alt:59.52 ax:624 ay:36 az:-16816 wx:-243 wy:-55 wz:-163 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.041 tm:3460.00 t:30.20 p:100610 alt:59.69 ax:624 ay:16 az:-17012 wx:-261 wy:-89 wz:-163 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.040 tm:3500.00 t:30.20 p:100605 alt:60.11 ax:456 ay:-48 az:-16808 wx:-245 wy:-66 wz:-169 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.039 tm:3539.00 t:30.20 p:100606 alt:60.03 ax:436 ay:-88 az:-16800 wx:-251 wy:-40 wz:-147 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.040 tm:3579.00 t:30.20 p:100608 alt:59.86 ax:620 ay:32 az:-16848 wx:-262 wy:-50 wz:-157 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.041 tm:3620.00 t:30.20 p:100602 alt:60.36 ax:560 ay:28 az:-16912 wx:-272 wy:-28 wz:-154 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.040 tm:3660.00 t:30.20 p:100609 alt:59.77 ax:652 ay:548 az:-16960 wx:119 wy:183 wz:-268 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.040 tm:3700.00 t:30.20 p:100601 alt:60.44 ax:1108 ay:-604 az:-15632 wx:-449 wy:-135 wz:-11 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.041 tm:3741.00 t:30.20 p:100612 alt:59.52 ax:796 ay:-112 az:-16296 wx:-357 wy:-523 wz:-773 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.040 tm:3781.00 t:30.20 p:100611 alt:59.61 ax:64 ay:464 az:-17420 wx:-3037 wy:-4332 wz:-314 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.040 tm:3821.00 t:30.20 p:100610 alt:59.69 ax:-164 ay:668 az:-16992 wx:-168 wy:-355 wz:1685 cx:24508.800 cy:7121.720 cz:3551.200

Подключение кардридера

Проверка работы кардридера

1. Нужно собрать на макетной плате нарисованную выше схему

2. В кардридер нужно вставить micro-SD карту, предварительно убедившись, что на ней ничего нет (на всякий случай)

3. В Arduino IDE нужно вписать следующий код (его также можно взять здесь: https://github.com/MihanEntalpo/flight-computer-app/blob/master/sdcard_write.ino):

#include <SPI.h>
#include <SD.h>
 
File myFile;
 
void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
 
 
  Serial.print("Initializing SD card...");
 
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
 
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
 
  // myFile = SD.open("test.txt", FILE_WRITE);
  myFile = SD.open("test.txt", O_WRITE | O_CREAT);
 
 
  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
 
  // re-open the file for reading:
  myFile = SD.open("test.txt");
  if (myFile) {
 
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      Serial.write(myFile.read());
    }
    // close the file:
    myFile.close();
 
    Serial.println("file.txt successfuly writen");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}
 
void loop() {
  // nothing happens after setup
}

4. Подключить Arduino к ПК
5. Загрузить скетч на Arduino
6. Открыть монитор порта и выбрать для него правильную скорость порта (230400, как в коде)
7. Посмотреть выдачу данных в мониторе, там должно быть написано

Initializing SD card...
initialization done.
file.txt successfuly writen

8. После этого, нужно вытащить SD-карту из кардридера, подключённого к Arduino и, вставив её в ПК или другое устройство, убедиться, что на карте есть файл «file.txt», в котором записана строка текста «testing sd card write»

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

Компоненты устройства:
1. Платформа Arduino
2. Модуль гироскопа + акселерометра + высотомера
3. Модуль записи на SD-карте + SD-карта
4. Батарея питания
5. Тумблер отключения питания
6. Кнопка для сброса высоты
7. Светодиоды-индикаторы взвода и зажигания
8. Реле для замыкания внешней цепи при зажигании
9. Несколько резисторов, указанных на схеме

Принципы работы устройства:
1. Устройство непрерывно замеряет текущую высоту с помощью датчика высоты, и вычисляет среднее значение за последние 16 измерений — скользящее среднее (это нужно для сглаживания колебаний измерения, так как они довольно сильно «скачут»).
2. Если нажата кнопка сброса высоты, текущее скользящее среднее высоты берётся в качестве начала отсчёта, и, в дальнейшем измеряется так называемая «высота от точки старта» — это высота за вычетом данного начала отсчёта. Соответственно, в месте, где кнопка была нажата, будет 0 этой высоты.
3. В ходе вычислений замеряется максимальная «высота от точки старта», после достижения которой 10 метров (например), устройство переходит в состояние «взвод», максимальная высота продолжает замеряться (любая измеренная высота больше максимальной — объявляется новой максимальной), при этом зажигается светодиод взвода
4. Если, находясь в состоянии «взвод» замерянная высота окажется меньше максимальной на 1 метр (например), устройство переходит в состояние «запуск», замыкая реле, и зажигая светодиод запуска. Светодиод взвода при этом гасится. Реле может выполнить любую работу, например, подать напряжение на сервопривод, выпускающий парашют.
5. Через 2 секунды после «запуска» реле размыкается, светодиод гасится, устройство переходит в режим «работа выполнена» и дальше ничего уже делать не будет.

Общая схема подключения компонентов:

Модули, необходимые для приложения:

Для работы программы я собрал несколько библиотек, либо не входящих в репозиторий Arduino, либо других версий, которые удалось заставить работать.

Библиотека BMP085:
BMP085.cpp
BMP085.h

Библиотека HMC5883L
HMC5883L.cpp
HMC5883L.h

Библиотека I2C
I2Cdev.cpp
I2Cdev.h

Библиотека MPU6050
MPU6050.cpp
MPU6050.h

Все эти модули можно найти вместе с основным файлом программы в репозитории.

Код приложения:

#include <Wire.h>
#include "BMP085.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "HMC5883L.h"
#include <SPI.h>
#include <Wire.h>
#include "BMP085.h"
#include "I2Cdev.h"
#include "MPU6050.h"
#include "HMC5883L.h"
#include <SPI.h>
#include <SD.h>
 
#define TRUE 1
#define FALSE 0
 
File myFile;
 
HMC5883L compass;
BMP085 pressure_m;
MPU6050 accelgyro;
 
//Контакт, куда подключено рэле
#define EXEC_PIN 3
//Контакт, куда подключена кнопка "сброс высоты"
#define RESET_ALT_PIN 6
//Контакт, куда подключен светодиод "взвод" (красный)
#define ARMED_PIN 9
//Время в течении которого надо держать рэле замкнутым, в миллисекундах
#define FIRE_TIME 6000
//Количество итераций для вычисления скользящего среднего высоты (лучше, чтобы было степенью двойки)
#define RUN_ALT_NUM 16
 
//Контакт, куда подключена кнопка "положение X"
#define POS_X_PIN 7
//Контакт, куда подключена кнопка "положение Y"
#define POS_Y_PIN 8
//Контакт, куда подключена кнопка "положение Z"
#define POS_Z_PIN 10
 
 
int16_t ax, ay, az;
int16_t gx, gy, gz;
bool blinkState = false;
double dt = 0;
long counter = 0;
double t0 = -1;
double millis_t = 0;
double io_t = 0;
double io_dt = 40;
double fired_millis_t = 0;
 
float base_altitude = 0;
 
float run_alt[RUN_ALT_NUM];
int run_alt_index = 0;
int last_run_alt_index = 0;
int run_alt_count = 0;
float run_alt_summ = 0;
 
bool is_prepared = FALSE;
bool is_armed = FALSE;
bool is_fired = FALSE;
bool is_led_armed = FALSE;
bool is_led_fired = FALSE;
bool is_done = FALSE;
 
float arming_altitude = 10;
float fire_minus_altitude = 1;
float max_altitude = 0;
 
char filename[] = "GY88_000.TXT";
 
void reset_running_alt();
void reset_alt(float current_alt);
 
/**
 * Сгенерировать имя файла
 */
void generateFileName()
{
  for (int i = 0; i< 1000; i++)
  {
    filename[5] = i/100 + '0';
    filename[6] = (i%100)/10 + '0';
    filename[7] = (i%10) + '0';
    if (SD.exists(filename)) continue;
    Serial.print("File: ");
    Serial.println(filename);
    break;  
  }
}
 
/**
 * Отладочная функция, вычисляет сумму скользящего среднего "по-настоящему"
 */
float debug_get_alt_summ()
{
  float summ = 0;
  for (int i=0; i< run_alt_count; i++)
  {
    summ += run_alt[i];
  }
  return summ;
}
 
void setup(){
    //Настраиваем режимы работы для цифровых контактов
    pinMode(EXEC_PIN, OUTPUT);
    pinMode(RESET_ALT_PIN, INPUT);
    pinMode(ARMED_PIN, OUTPUT);
    //Сбрасываем значения для скользящего среднего по высоте
    reset_running_alt();
 
    //Включаем последовательный порт на максимальную скорость
    Serial.begin(230400);
    //while (!Serial) {
      ; // wait for serial port to connect. Needed for native USB port only
    //}    
 
    Serial.print("Initializing SD card...");
 
    if (!SD.begin(4)) {
      Serial.println("initialization failed!");
      return;
    }
    Serial.println("initialization done.");
 
    //Сгенерируем имя файла
    generateFileName();
    myFile = SD.open(filename, O_WRITE | O_CREAT);
 
    //Включаем протокол Wire
    Wire.begin();
    TWBR = 24;
    Serial.println("Initializing I2C devices...");    
    //инициализируем акселерометр с гироскопом
    accelgyro.initialize();
    accelgyro.setMasterClockSpeed(13);
    accelgyro.setI2CMasterModeEnabled(true);
    //инициализируем компас
    compass = HMC5883L();
    setupHMC5883L(); 
 
    //проверяем содеинение
    Serial.println("Testing device connections...");
    Serial.println(accelgyro.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed");
 
    //калибруем датчик давления
    pressure_m.bmp085Calibration();
}
 
void loop()
{
  millis_t = millis();
  //Считаем температуру (её знает датчик давления)
  float temperature = pressure_m.bmp085GetTemperature(); //MUST be called first
  //Считаем давление
  float pressure = pressure_m.bmp085GetPressure();
  //Вычислим высоту над уровнем моря
  float altitude = pressure_m.calcAltitude(pressure);
  float altitude_b = altitude - base_altitude;
 
  //Проведём вычисления для скользящего среднего по высоте
  run_alt[run_alt_index] = altitude_b;
  //run_alt_summ += altitude_b;
  if (run_alt_count == RUN_ALT_NUM)
  {
    last_run_alt_index = (run_alt_index == 0) ? RUN_ALT_NUM - 1 : run_alt_index - 1;
    //run_alt_summ -= run_alt[last_run_alt_index];
  }
  else
  {
    run_alt_count +=1;
  }
  run_alt_index = (run_alt_index + 1 == RUN_ALT_NUM) ? 0 : run_alt_index + 1;
 
  //Временно вычисляем медленным методом
  run_alt_summ = debug_get_alt_summ();
 
  float run_altitude = run_alt_summ / (float) run_alt_count;
 
  //Вычислим dt (интервал между замерами в миллисекундах)
  if (t0 < 0)
  {
    t0 = millis_t;
  }
  else
  {
    dt = millis_t - t0;
    t0 = millis_t;
  }
 
  if (run_altitude > max_altitude)
  {
    max_altitude = run_altitude;
  }
 
 
  if (millis_t - io_t > io_dt)
  {
    io_t = millis_t;
    if (digitalRead(RESET_ALT_PIN) == HIGH)
    {
      reset_alt(altitude);
    }
 
 
 
    if (!is_done)
    {
      if (!is_fired)
      {
        if (is_prepared)
        {
          if (!is_armed)
          {
            if (max_altitude > arming_altitude)
            {
              is_armed = TRUE;
            }
          }
          else
          {
            if (max_altitude - run_altitude > fire_minus_altitude)
            {
              is_armed = FALSE;
              is_fired = TRUE;
              fired_millis_t = millis_t;
            }
          }
        }
      }
      else
      {
        if (millis_t - fired_millis_t > FIRE_TIME)
        {
          is_fired = FALSE;
          is_done = TRUE;
          is_prepared = FALSE;
        }
      }
    }
 
    if (is_armed != is_led_armed)
    {
      is_led_armed = is_armed;
      digitalWrite(ARMED_PIN, is_armed ? HIGH : LOW);  
    }
 
    if (is_fired != is_led_fired)
    {
      is_led_fired = is_fired;
      digitalWrite(EXEC_PIN, is_fired ? HIGH : LOW);
    }
 
  }
 
  //Интервал между замерами
  Serial.print("dt:"); Serial.print(dt / 1000, 3);
  myFile.print("dt:"); myFile.print(dt / 1000, 3);
  //Сколько времени прошло с запуска?
  Serial.print(" tm:"); Serial.print(millis_t);
  myFile.print(" tm:"); myFile.print(millis_t);
  //Температура, в градусах цельсия
  Serial.print(" t:"); Serial.print(temperature, 2); 
  myFile.print(" t:"); myFile.print(temperature, 2); 
  //Давление, в паскалях
  Serial.print(" p:"); Serial.print(pressure, 0); 
  myFile.print(" p:"); myFile.print(pressure, 0); 
  //Высота над уровнем моря, в метрах
  Serial.print(" alt:"); Serial.print(altitude, 2); 
  myFile.print(" alt:"); myFile.print(altitude, 2); 
  //Высота над базовым уровнем, в метрах
  Serial.print(" altb:"); Serial.print(altitude_b, 2); 
  myFile.print(" altb:"); myFile.print(altitude_b, 2);
  //Высота над базовым уровнем, в метрах, скользящее среднее
  Serial.print(" altr:"); Serial.print(run_altitude, 2); 
  myFile.print(" altr:"); myFile.print(run_altitude, 2);
  //Высота над базовым уровнем, в метрах, максимальная
  Serial.print(" Malt:"); Serial.print(max_altitude, 2); 
  myFile.print(" Malt:"); myFile.print(max_altitude, 2);
 
  //Статусы:
  Serial.print(" P"); Serial.print(is_prepared ? "1" : "0"); 
  myFile.print(" P"); myFile.print(is_prepared ? "1" : "0");
  Serial.print(" A"); Serial.print(is_armed ? "1" : "0"); 
  myFile.print(" A"); myFile.print(is_armed ? "1" : "0");
  Serial.print(" F"); Serial.print(is_fired ? "1" : "0"); 
  myFile.print(" F"); myFile.print(is_fired ? "1" : "0");
  Serial.print(" D"); Serial.print(is_done ? "1" : "0"); 
  myFile.print(" D"); myFile.print(is_done ? "1" : "0");
 
  //Считаем значения с магнитометра
  MagnetometerScaled scaled = compass.ReadScaledAxis();
  //Считаем значения с акселерометра и гироскопа
  accelgyro.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
 
  //Данные акселерометра:
  Serial.print(" ax:"); Serial.print(ax);
  Serial.print(" ay:"); Serial.print(ay);
  Serial.print(" az:"); Serial.print(az);
  myFile.print(" ax:"); myFile.print(ax);
  myFile.print(" ay:"); myFile.print(ay);
  myFile.print(" az:"); myFile.print(az);
  //Данные гироскопа
  Serial.print(" wx:"); Serial.print(gx);
  Serial.print(" wy:"); Serial.print(gy);
  Serial.print(" wz:"); Serial.print(gz);
  myFile.print(" wx:"); myFile.print(gx);
  myFile.print(" wy:"); myFile.print(gy);
  myFile.print(" wz:"); myFile.print(gz);
  //Данные магнитометра    
  Serial.print(" cx:"); Serial.print(scaled.XAxis, 3);
  Serial.print(" cy:"); Serial.print(scaled.YAxis, 3);
  Serial.print(" cz:"); Serial.print(scaled.ZAxis, 3);
  Serial.println();
  myFile.print(" cx:"); myFile.print(scaled.XAxis, 3);
  myFile.print(" cy:"); myFile.print(scaled.YAxis, 3);
  myFile.print(" cz:"); myFile.print(scaled.ZAxis, 3);
  myFile.println();
 
  myFile.flush();
}
 
//Настройка модуля HMC5883L (компаса)
void setupHMC5883L(){
  //Setup the HMC5883L, and check for errors
  int error;  
  error = compass.SetScale(1.3); //Set the scale of the compass.
  if(error != 0) Serial.println(compass.GetErrorText(error)); //check if there is an error, and print if so
 
  error = compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous
  if(error != 0) Serial.println(compass.GetErrorText(error)); //check if there is an error, and print if so
}
 
/**
 * Сброс высоты и перевод в состояние готовности - выполняется по нажатию кнопки
 */
void reset_alt(float current_alt)
{
  base_altitude = current_alt;
  is_prepared = TRUE;
  is_armed = FALSE;
  max_altitude = 0;  
  reset_running_alt();
}
 
/**
 * Сброс скользящего среднего для высоты
 */
void reset_running_alt()
{
  run_alt_index = 0;
  run_alt_count = 0;
  run_alt_summ = 0;
  last_run_alt_index = 0;
  for (int i =0; i<RUN_ALT_NUM; i++)
    {
      run_alt[i]=0;
    }
}

Данные, выдаваемые программой, должны быть примерно такими:

dt:0.048 tm:5760.00 t:26.80 p:100898 alt:35.61 altb:35.61 altr:35.60 Malt:35.60 P0 A0 F0 D0 ax:-1084 ay:16 az:-15708 wx:-282 wy:66 wz:-68 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:5807.00 t:26.80 p:100898 alt:35.61 altb:35.61 altr:35.61 Malt:35.61 P0 A0 F0 D0 ax:-1132 ay:100 az:-16276 wx:-282 wy:213 wz:-81 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.048 tm:5855.00 t:26.80 p:100898 alt:35.61 altb:35.61 altr:35.63 Malt:35.63 P0 A0 F0 D0 ax:-1016 ay:16 az:-15740 wx:-279 wy:149 wz:-78 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:5902.00 t:26.80 p:100895 alt:35.86 altb:35.86 altr:35.66 Malt:35.66 P0 A0 F0 D0 ax:-1152 ay:-48 az:-15596 wx:-247 wy:60 wz:-60 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:5949.00 t:26.80 p:100904 alt:35.11 altb:35.11 altr:35.64 Malt:35.66 P0 A0 F0 D0 ax:-1080 ay:-52 az:-15704 wx:-276 wy:54 wz:-61 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:5996.00 t:26.80 p:100902 alt:35.27 altb:35.27 altr:35.62 Malt:35.66 P0 A0 F0 D0 ax:-1152 ay:-44 az:-15652 wx:-290 wy:69 wz:-79 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6043.00 t:26.80 p:100900 alt:35.44 altb:35.44 altr:35.60 Malt:35.66 P0 A0 F0 D0 ax:-1128 ay:-8 az:-15496 wx:-281 wy:37 wz:-70 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6090.00 t:26.80 p:100897 alt:35.69 altb:35.69 altr:35.60 Malt:35.66 P0 A0 F0 D0 ax:-1048 ay:24 az:-15620 wx:-280 wy:59 wz:-75 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6137.00 t:26.80 p:100902 alt:35.27 altb:35.27 altr:35.55 Malt:35.66 P0 A0 F0 D0 ax:-1164 ay:0 az:-15736 wx:-292 wy:73 wz:-69 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6184.00 t:26.80 p:100899 alt:35.52 altb:35.52 altr:35.56 Malt:35.66 P0 A0 F0 D0 ax:-1088 ay:204 az:-15404 wx:-296 wy:-9 wz:-38 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.048 tm:6232.00 t:26.80 p:100897 alt:35.69 altb:35.69 altr:35.57 Malt:35.66 P0 A0 F0 D0 ax:-1220 ay:-340 az:-15976 wx:-261 wy:32 wz:-106 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.048 tm:6280.00 t:26.80 p:100902 alt:35.27 altb:35.27 altr:35.59 Malt:35.66 P0 A0 F0 D0 ax:-880 ay:144 az:-15828 wx:-289 wy:120 wz:-76 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6327.00 t:26.80 p:100906 alt:34.94 altb:34.94 altr:35.53 Malt:35.66 P0 A0 F0 D0 ax:-1300 ay:52 az:-15292 wx:-274 wy:97 wz:-71 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6374.00 t:26.80 p:100903 alt:35.19 altb:35.19 altr:35.50 Malt:35.66 P0 A0 F0 D0 ax:-1072 ay:-76 az:-15452 wx:-264 wy:55 wz:-74 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6421.00 t:26.80 p:100905 alt:35.02 altb:35.02 altr:35.44 Malt:35.66 P0 A0 F0 D0 ax:-1040 ay:-4 az:-15844 wx:-297 wy:70 wz:-80 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6468.00 t:26.80 p:100901 alt:35.36 altb:35.36 altr:35.40 Malt:35.66 P0 A0 F0 D0 ax:-1108 ay:64 az:-15644 wx:-294 wy:88 wz:-65 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6515.00 t:26.80 p:100901 alt:35.36 altb:35.36 altr:35.39 Malt:35.66 P0 A0 F0 D0 ax:-1136 ay:80 az:-15628 wx:-287 wy:75 wz:-62 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6562.00 t:26.80 p:100903 alt:35.19 altb:35.19 altr:35.36 Malt:35.66 P0 A0 F0 D0 ax:-1120 ay:-136 az:-15608 wx:-256 wy:37 wz:-76 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.047 tm:6609.00 t:26.80 p:100895 alt:35.86 altb:35.86 altr:35.38 Malt:35.66 P0 A0 F0 D0 ax:-1032 ay:-8 az:-15600 wx:-283 wy:81 wz:-68 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.048 tm:6657.00 t:26.80 p:100900 alt:35.44 altb:35.44 altr:35.35 Malt:35.66 P0 A0 F0 D0 ax:-1112 ay:-16 az:-15612 wx:-283 wy:59 wz:-77 cx:24508.800 cy:7121.720 cz:3551.200
dt:0.048 tm:6705.00 t:26.80 p:100901 alt:35.36 altb:35.36 altr:35.37 Malt:35.66 P0 A0 F0 D0 ax:-1084 ay:64 az:-15624 wx:-282 wy:73 wz:-66 cx:24508.800 cy:7121.720 cz:3551.200

Что означают данные:
dt — интервал времени в миллисекундах с прошлого замера
tm — количество миллисекунд, прошедшее с включения устройства
t — температура воздуха
p — давление воздуха
alt — высота по данным датчика-высотомера
altb — высота за вычетом «базовой» высоты, полученной при нажатии кнопки
altr — скользящее среднее по последним 16 замерам высота altb
Значения-флаги (могут принимать значение 0 или 1, что означает НЕТ и ДА)
P — Была ли нажата кнопка установки базовой высоты, т.е. подготовлено ли устройство? (Prepared)
A — Взведено ли устройство? Т.е. пройдена ли точка минимальной необходимой для взвода высоты (10 метров) (Armed)
F — Активировано ли реле? т.е. выполнено ли условие снижения не 1 метр относительно максимальной набранной высоты? (Fired)
D — Закончена ли работа устройства? Этот флаг включается, когда выключается флаг F. После этого устройство больше никаких функций выполнять не будет, и для работы с ним его потребуется либо перезагрузить либо выключить и включить снова.

При этом те же самые данные программа будет писать на SD-карту, вставленную в кардридер. При каждом запуске устройства будет создаваться новый файл со следующим порядковым номером.

Результаты сборки устройства:

Как сделать парашют для водяной ракеты:

Отлично, реле, замыкающее контакты при начале падения у нас есть. Есть много способов как использовать это для выброса парашюта, из которого выберем следующий:
1) Привяжем парашют к бутылке-ракете
2) Приложим его к головной части ракеты, и прижмём его куском другой пластиковой бутылки
3) Прижимающий кусок пластика соединим тонкой полимерной верёвочкой (например, капроновой)
4) на верёвочке намотаем пару оборотов нихромовой нити, которую подключим к батарейке, а в разрыв цепи вставим реле, которое как раз у нас уже готово к работе.

В результате получится: при срабатывании реле нихромовая нить нагревается, пережигает верёвочку, и воздушным потоком срывает пластиковую накладку и парашют вырывается на свободу.

Вот как это выглядит:

Ракета:

Ракета с упакованным парашютом:

А вот что получилось в результате пробного запуска (Видео снято на тапок):

Дальнейшие планы

1) Первая планируемая доработка — сохранять файлы на SD-карту в формате CSV с заголовком, чтобы облегчить дальнейшую работу с ними в программах типа Excel и Libreoffice. Сейчас приходится сначала очищать данные в текстовом редакторе.

2) Также, по аналогии с кнопкой сброса высоты, планируется добавить кнопки сброса линейных ускорений — для того, чтобы можно было, по очереди поворачивая устройство в плоскости параллельные горизонтальной и двум вертикальным, и нажимая соответствующие кнопки, «объяснить» устройству как соотносятся его оси и горизонтальная плоскость земли, и какие значения датчика линейных ускорений нужно считать нулевыми

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

4) Датчик ускорения по умолчанию настроен на максимальное ускорение в 2G. Судя по документации, его можно настроить на работу с максимальными ускорениями в 16G, но сделать этого пока не удалось. А стоило бы.


5 комментариев

  • Ответить Иван |

    Здравствуйте, очень понравился ваш проект, хочу сделать что-то подобное. Но только на ардуино нано (из за габаритов). Есть ли у вас такой же проект, только для нано? Или просто инструкция, как сделать тоже самое на ардуино нано? Спасибо

    • Ответить mihanentalpo |

      Как ни странно, этот проект и так сделан на Arduino Nano. Обычное Arduino нарисовано в статье просто для иллюстраци

  • Ответить mihanentalpo |

    Контакты у Arduino nano такие же как у Arduino uno, поэтому сделать такую схему на nano — никаких проблем ( собственно она и была сделана на nano, что видно из фотографий)

  • Ответить Борис |

    Насколько точны показания продолжения в пространстве? Можно ли использовать несколько датчиков в одной системе?

    • Ответить mihanentalpo |

      Достаточно точны, но только по вертикали. Пока тестировал — подниманием руки с контролером вверх и вниз добился плавного изменения высоты по датчику. По другим осям так и не дошли руки сделать вычисления на базе акселерометра.
      Думаю несколько можно использовать. Только зачем?

Оставить комментарий