GPS-телеметрия для любительского картинга

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

А что делать прокатчику, если хочется не просто «бомбить» трассу, а работать над улучшением результатов? Из точных данных — лишь распечатка по итогам заездов. Кто-то снимает свои круги на видео, кто-то пользуется фитнес-трекерами и друзьями с секундомерами.

Мы решили сделать свою — простую и бюджетную — телеметрию. За основу взяли GPS-модуль и скрестили его с акселерометром. Данные пишутся на SD карту в формате, который напрямую импортируется на Google Maps и Яндекс.Карты.

  • Платформы: Arduino Leonardo, Iskra Neo
  • Языки программирования: Arduino (C++)
  • Тэги: GPS, GLONASS, MP3, IMU, телеметрия, автогонки

Видеообзор

Что это

Базовое устройство

Простое устройство, повторяющее мини-проект «GPS-трекера».

Координаты и скорость определяются приёмником спутниковой навигации и вместе с данными акселерометра записываются на microSD с помощью картридера. Запись трека включается и выключается светодиодной кнопкой.

Данные сохраняются в электронную таблицу dataGPS.csv, формат которой соответствует требованиям сервиса «Яндекс. Народная карта».

Что понадобится

Программный код

racing-gps-tracker-slot-box.ino
// библиотека для работы I²C
#include <Wire.h>
// библиотека для работы с модулями IMU
#include <TroykaIMU.h>
// библиотека для работы с устройствами по SPI
#include <SPI.h>
// библиотека для работы с SD-картой
#include <SD.h>
// библиотека для работы с GPS устройством
#include <TroykaGPS.h>
 
// создаём объект класса GPS и передаём в него объект Serial1 
GPS gps(Serial1);
// создаём объект для работы с акселерометром
Accelerometer accel;
 
// пин светодиода
#define LED_PIN             A0
// пин кнопки
#define BUTTON_PIN          13
// пин CS micro-sd карты
#define SD_CHIP_SELECT_PIN  9
// интервал времени записи данных на карту
#define INTERVAL            5000
// задаём размер массива для времени, даты, широты и долготы
#define MAX_SIZE_MASS 16
// массив для хранения текущего времени
char time[MAX_SIZE_MASS];
// состояние записи на карту памяти
bool stateRec = false;
// запоминаем текущее время
long startMillis = millis();
// счётчк записи
int counter = 0;
 
void setup()
{
  // устанавливаем светодиод в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // устанавливаем кнопку в режим входа
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // открываем последовательный порт для мониторинга действий в программе
  Serial.begin(115200);
  // ждём, пока не откроется монитор последовательного порта
  // для того, чтобы отследить все события в программе
  while (!Serial) {
  }
  Serial.print("Serial init OK\r\n");
  // открываем Serial-соединение с GPS-модулем
  Serial1.begin(115200);
  // выводим информацию об инициализации в Serial-порт
  Serial.println("Initializing SD card...");
  // инициализируем SD-карту
  while (!SD.begin(SD_CHIP_SELECT_PIN)) {
    Serial.println("Card failed, or not present");
    delay(1000);
  }
  // выводим информацию в Serial-порт
  Serial.println("Card initialized");
  // выводим сообщение о начале инициализации акселерометра
  Serial.println("Initializing accelerometer...");
  // инициализация акселерометра
  accel.begin();
  // устанавливаем чувствительность акселерометра
  // 2g — по умолчанию, 4g, 8g
  accel.setRange(RANGE_2G);
  // выводим сообщение об удачной инициализации
  Serial.println("Accelerometer initialized");
}
 
void loop()
{
  // Фиксируем нажатие кнопки
  if (digitalRead(BUTTON_PIN)) {
    // меняем состояние «запись» / «не запись» на карту памяти 
    stateRec = !stateRec;
    // меняем состояние светодиода индикации
    digitalWrite(LED_PIN, stateRec);
  }
 
  // если пришли данные с gps-модуля
  if (gps.available()) {
    // считываем данные и парсим
    gps.readParsing();
    // считываем состояние GPS-модуля
    switch(gps.getState()) {
      // всё OK
      case GPS_OK:
        Serial.println("GPS is OK");
        // если прошёл заданный интервал времени и запись данных включена
        if (millis() - startMillis > INTERVAL && stateRec) {
          // сохраняем данные GPS и акселерометра на карту памяти
          saveSD();
          // запоминаем текущее время
          startMillis = millis();
        }
        break;
      // ошибка данных
      case GPS_ERROR_DATA:
        Serial.println("GPS error data");
        break;
      // нет соединение со спутниками
      case GPS_ERROR_SAT:
        Serial.println("GPS no connect to satellites");
        break;
    }
  }
}
 
// функция сохарение данных на карту памяти
void saveSD() {
  File dataFile = SD.open("dataGPS.csv", FILE_WRITE);
  // если файл существует и открылся
  if (dataFile) {
    // считывает текущее время
    gps.getTime(time, MAX_SIZE_MASS);
    // записываем время на карту памяти
    // считываем и записываем координаты широты и долготы на карту памяти
    dataFile.print(gps.getLatitudeBase10(), 6);
    dataFile.print("\t");
    dataFile.print(gps.getLongitudeBase10(), 6);
    dataFile.print("\t");
    dataFile.print(counter++);
    dataFile.print("\t");
    dataFile.print(gps.getSpeedKm());
    dataFile.print("km/h");
    dataFile.print("\t");
    dataFile.print(accel.readAX());
    dataFile.print("\t");
    dataFile.print(accel.readAX());
    dataFile.print("\t");
    dataFile.println(time);
    // записываем данные направления и величины ускорения в м/с² по оси X и Y
    dataFile.close();
    Serial.println("Save OK");
  } else {
    Serial.println("Error opening dataGPS.csv");
  }
}

Устройство в герметичном корпусе

Главный слоган данного девайса: «Безопасность и надёжность». За основу взят «GPS-трекер», с небольшими изменениями:

Благодаря данным изменениям устройство сможет работать и в снег, и в дождь.

Что понадобится

Программный код

racing-gps-tracker-security.ino
// библиотека для работы I²C
#include <Wire.h>
// библиотека для работы с модулями IMU
#include <TroykaIMU.h>
// библиотека для работы с устройствами по SPI
#include <SPI.h>
// библиотека для работы с SD-картой
#include <SD.h>
// библиотека для работы с GPS устройством
#include <TroykaGPS.h>
 
// создаём объект класса GPS и передаём в него объект Serial1 
GPS gps(Serial1);
// создаём объект для работы с акселерометром
Accelerometer accel;
 
// пин светодиода
#define LED_PIN             A4
// пин кнопки
#define BUTTON_PIN          8
// пин CS micro-sd карты
#define SD_CHIP_SELECT_PIN  9
// интервал времени записи данных на карту
#define INTERVAL            5000
// задаём размер массива для времени, даты, широты и долготы
#define MAX_SIZE_MASS 16
// массив для хранения текущего времени
char time[MAX_SIZE_MASS];
// состояние записи на карту памяти
bool stateRec = false;
// запоминаем текущее время
long startMillis = millis();
// счётчк записи
int counter = 0;
 
void setup()
{
  // устанавливаем светодиод в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // устанавливаем кнопку в режим входа
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // открываем последовательный порт для мониторинга действий в программе
  Serial.begin(115200);
  // ждём, пока не откроется монитор последовательного порта
  // для того, чтобы отследить все события в программе
  while (!Serial) {
  }
  Serial.print("Serial init OK\r\n");
  // открываем Serial-соединение с GPS-модулем
  Serial1.begin(115200);
  // выводим информацию об инициализации в Serial-порт
  Serial.println("Initializing SD card...");
  // инициализируем SD-карту
  while (!SD.begin(SD_CHIP_SELECT_PIN)) {
    Serial.println("Card failed, or not present");
    delay(1000);
  }
  // выводим информацию в Serial-порт
  Serial.println("Card initialized");
  // выводим сообщение о начале инициализации акселерометра
  Serial.println("Initializing accelerometer...");
  // инициализация акселерометра
  accel.begin();
  // устанавливаем чувствительность акселерометра
  // 2g — по умолчанию, 4g, 8g
  accel.setRange(RANGE_2G);
  // выводим сообщение об удачной инициализации
  Serial.println("Accelerometer initialized");
}
 
void loop()
{
  // Фиксируем нажатие кнопки
  if (digitalRead(BUTTON_PIN)) {
    // меняем состояние «запись» / «не запись» на карту памяти 
    stateRec = !stateRec;
    // меняем состояние светодиода индикации
    digitalWrite(LED_PIN, stateRec);
  }
 
  // если пришли данные с gps-модуля
  if (gps.available()) {
    // считываем данные и парсим
    gps.readParsing();
    // считываем состояние GPS-модуля
    switch(gps.getState()) {
      // всё OK
      case GPS_OK:
        Serial.println("GPS is OK");
        // если прошёл заданный интервал времени и запись данных включена
        if (millis() - startMillis > INTERVAL && stateRec) {
          // сохраняем данные GPS и акселерометра на карту памяти
          saveSD();
          // запоминаем текущее время
          startMillis = millis();
        }
        break;
      // ошибка данных
      case GPS_ERROR_DATA:
        Serial.println("GPS error data");
        break;
      // нет соединение со спутниками
      case GPS_ERROR_SAT:
        Serial.println("GPS no connect to satellites");
        break;
    }
  }
}
 
// функция сохарение данных на карту памяти
void saveSD() {
  File dataFile = SD.open("dataGPS.csv", FILE_WRITE);
  // если файл существует и открылся
  if (dataFile) {
    // считывает текущее время
    gps.getTime(time, MAX_SIZE_MASS);
    // записываем время на карту памяти
    // считываем и записываем координаты широты и долготы на карту памяти
    dataFile.print(gps.getLatitudeBase10(), 6);
    dataFile.print("\t");
    dataFile.print(gps.getLongitudeBase10(), 6);
    dataFile.print("\t");
    dataFile.print(counter++);
    dataFile.print("\t");
    dataFile.print(gps.getSpeedKm());
    dataFile.print("km/h");
    dataFile.print("\t");
    dataFile.print(accel.readAX());
    dataFile.print("\t");
    dataFile.print(accel.readAX());
    dataFile.print("\t");
    dataFile.println(time);
    // записываем данные направления и величины ускорения в м/с² по оси X и Y
    dataFile.close();
    Serial.println("Save OK");
  } else {
    Serial.println("Error opening dataGPS.csv");
  }
}

Трекер с MP3-магнитолой

Мы пошли дальше и решили скреативить. Сделаем стильный музыкальный корпус устройства из #структора, детского железного конструктора и зальём всё это парой баллонов краски:

Что понадобится

Программный код

racing-gps-tracker-slot-box-xl.ino
// библиотека программного Serial-порта
#include <SoftwareSerial.h>
// библиотека для работы с mp3 плеером
#include <DFPlayer_Mini_Mp3.h>
// библиотека для работы I²C
#include <Wire.h>
// библиотека для работы с модулями IMU
#include <TroykaIMU.h>
// библиотека для работы с устройствами по SPI
#include <SPI.h>
// библиотека для работы с SD-картой
#include <SD.h>
// библиотека для работы с GPS устройством
#include <TroykaGPS.h>
 
// создаём объект класса GPS и передаём в него объект Serial1 
GPS gps(Serial1);
// создаём объект для работы с акселерометром
Accelerometer accel;
// инициализируем новый последовательный порт
// RX 10, TX 11
SoftwareSerial mp3Serial(10, 11);
 
// пин воспрозведение музыки
#define PLAY_PIN            7
// пин увеличение громкости
#define VOLUME_UP_PIN       A5
// пин уменьшение громкости
#define VOLUME_DOWN_PIN     A4
// пин светодиода
#define LED_PIN             A0
// пин кнопки
#define BUTTON_PIN          13
// пин CS micro-sd карты
#define SD_CHIP_SELECT_PIN  9
// интервал времени записи данных на карту
#define INTERVAL            5000
// задаём размер массива для времени, даты, широты и долготы
#define MAX_SIZE_MASS 16
// массив для хранения текущего времени
char time[MAX_SIZE_MASS];
// состояние записи на карту памяти
bool stateRec = false;
// запоминаем текущее время
long startMillis = millis();
// счётчк записи
int counter = 0;
// уровень громкости плеера
int volume = 15;
 
void setup()
{
  // устанавливаем светодиод в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // устанавливаем кнопку в режим входа
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // открываем последовательный порт для мониторинга действий в программе
  Serial.begin(115200);
  // ждём, пока не откроется монитор последовательного порта
  // для того, чтобы отследить все события в программе
  while (!Serial) {
  }
  Serial.print("Serial init OK\r\n");
  // открываем Serial-соединение с GPS-модулем
  Serial1.begin(115200);
  // выводим информацию об инициализации в Serial-порт
  Serial.println("Initializing SD card...");
  // инициализируем SD-карту
  while (!SD.begin(SD_CHIP_SELECT_PIN)) {
    Serial.println("Card failed, or not present");
    delay(1000);
  }
  // выводим информацию в Serial-порт
  Serial.println("Card initialized");
  // выводим сообщение о начале инициализации акселерометра
  Serial.println("Initializing accelerometer...");
  // инициализация акселерометра
  accel.begin();
  // устанавливаем чувствительность акселерометра
  // 2g — по умолчанию, 4g, 8g
  accel.setRange(RANGE_2G);
  // выводим сообщение об удачной инициализации
  Serial.println("Accelerometer initialized");
  // открываем программный Serial-порт
  mp3Serial.begin(9600);
  // выбор Serial для упрапвления mp3-плеером
  mp3_set_serial(mp3Serial);
  // установка громкости    
  mp3_set_volume(volume);
}
 
void loop()
{
  // если нажата кнопка воспроизведение музыки
  if (!digitalRead(PLAY_PIN)) {
    mp3_play();
    delay(100);
  }
  // если нажата кнопка увеличение громкости
  if (!digitalRead(VOLUME_UP_PIN)) {
    volume = volume + 5;
    delay(50);
    mp3_set_volume(volume);
  }
  // если нажата кнопка уменьшение громкости
  if (!digitalRead(VOLUME_DOWN_PIN)) {
    volume = volume - 5;
    delay(50);
    mp3_set_volume(volume);
  }
 
  // Фиксируем нажатие кнопки
  if (digitalRead(BUTTON_PIN)) {
    // меняем состояние «запись» / «не запись» на карту памяти 
    stateRec = !stateRec;
    // меняем состояние светодиода индикации
    digitalWrite(LED_PIN, stateRec);
  }
 
  // если пришли данные с gps-модуля
  if (gps.available()) {
    // считываем данные и парсим
    gps.readParsing();
    // считываем состояние GPS-модуля
    switch(gps.getState()) {
      // всё OK
      case GPS_OK:
        Serial.println("GPS is OK");
        // если прошёл заданный интервал времени и запись данных включена
        if (millis() - startMillis > INTERVAL && stateRec) {
          // сохраняем данные GPS и акселерометра на карту памяти
          saveSD();
          // запоминаем текущее время
          startMillis = millis();
        }
        break;
      // ошибка данных
      case GPS_ERROR_DATA:
        Serial.println("GPS error data");
        break;
      // нет соединение со спутниками
      case GPS_ERROR_SAT:
        Serial.println("GPS no connect to satellites");
        break;
    }
  }
}
 
// функция сохарение данных на карту памяти
void saveSD() {
  File dataFile = SD.open("dataGPS.csv", FILE_WRITE);
  // если файл существует и открылся
  if (dataFile) {
    // считывает текущее время
    gps.getTime(time, MAX_SIZE_MASS);
    // записываем время на карту памяти
    // считываем и записываем координаты широты и долготы на карту памяти
    dataFile.print(gps.getLatitudeBase10(), 6);
    dataFile.print("\t");
    dataFile.print(gps.getLongitudeBase10(), 6);
    dataFile.print("\t");
    dataFile.print(counter++);
    dataFile.print("\t");
    dataFile.print(gps.getSpeedKm());
    dataFile.print("km/h");
    dataFile.print("\t");
    dataFile.print(accel.readAX());
    dataFile.print("\t");
    dataFile.print(accel.readAX());
    dataFile.print("\t");
    dataFile.println(time);
    // записываем данные направления и величины ускорения в м/с² по оси X и Y
    dataFile.close();
    Serial.println("Save OK");
  } else {
    Serial.println("Error opening dataGPS.csv");
  }
}

Что дальше

Добавьте к устройству IMU-модуль на 10 степеней свободы и соберите настоящий «чёрный ящик» для легкомоторного самолёта.

Подключите GPRS-шилд и соберите автономную сигнализацию для мотоцикла или велосипеда.

Черпайте вдохновение для новых устройств в подборке мини-проектов для Arduino и Iskra Neo.