Продвинутый трекер для походов на Arduino: GPS, датчики и батарейный блок
Компоненты и материалы
- Arduino 101 — 1 шт.
- АА батареи (4 шт.) + держатель 4×AA — 1 компл.
- Accessory Shield (Waveshare) — 1 шт.
- Датчик давления BMP180 (Adafruit) — 1 шт.
- GPS-модуль Adafruit Ultimate GPS Breakout — 1 шт.
- Магнетометр 3-осевой (Adafruit HMC5883) — 1 шт.
- Датчик влажности AM2320 (Adafruit) — 1 шт.
- Элемент питания CR2012 (для RTC) — 2 шт.
- Резисторы: 10 кОм, 3.9 кОм — по 1 шт.
Необходимые инструменты
- Клеевой пистолет
- Паяльник
Необходимые инструменты и сервисы
- Arduino IDE
О проекте
Что такое Hiking Tracker?
Устройство для мониторинга изменения окружающей среды туриста во время похода: температура, давление, влажность, высота, местоположение, направление. Дает интересные сведения о процессе ходьбы.
В горах холодно, сухо, разреженный воздух — многим трудно дышать, бывает раздражение кожи. Это устройство помогает узнать свои пределы. Также интересно знать высоту! Не только горы, но и болота, пустыни, леса — у каждого уникальный экологический профиль, мониторинг в движении помогает лучше понять природу.

Видео-демонстрация
Шаг 1: Железо — что за что отвечает
Много датчиков. Accessory Shield имеет на борту I2C-устройства:
- LM75B — температура окружающей среды
- ADXL345 — 3-осевой акселерометр (наклон, гравитация)
- DS3231 — высокоточный RTC (время)
- HMC5883 — 3-осевой магнетометр (компас)
- AM2320 — влажность (% RH)
- BMP180 — атмосферное давление и высота
- MTK3339 GPS — координаты и пройденное расстояние
На самом Arduino Uno (через шилд):
- Делитель напряжения 3.9к + 22к — измерение напряжения 4×AA
- OLED 1306 — вывод данных
- Зуммер — сигнализация
- RGB LED — не используется
- XBee интерфейс — не используется, но пины D2, D3, D9, D10 доступны (D2 подключен к RESET, позволяет программный сброс)
- Джойстик и потенциометр — не используются
Шаг 2: Модификация и подключение железа
Arduino Uno смонтирована на держателе 4×AA с стойками и клеем.


Установлены 4×AA и CR1220 (для RTC).

Между держателем и Arduino размещаются датчики давления, влажности, магнитного поля и GPS.

I2C-датчики спаяны на куске монтажной платы и помещены в это пространство.

Подключение датчиков к Arduino с нижней стороны.

Добавлен делитель напряжения для измерения заряда АА-батарей.

Линия измерения напряжения АА идет на АЦП A1.

Джойстик шилда задействует A1 через A5, поэтому он выпаян и удален.

Паяльными перемычками на Accessory Shield (см. схему) выводятся D2 (soft reset), D3, D9, D10 на XBee-разъем.

Стек компонентов:
- Верхний слой: Accessory Shield на Arduino
- Средний слой: остальные датчики и GPS между Uno и батарейным отсеком
- Нижний слой: держатель 4×AA с батареями

Шаг 3: Программирование устройства
Использован Arduino IDE Build 1.8.5. Все библиотеки установлены через Library Manager или как ZIP.



После добавления библиотек изучены примеры для каждого датчика, определены API. Все заголовки включены в один скетч.
Список заголовочных файлов
math.h, inttypes.h, Wire.h, lm75.h, ADXL345.h, ChainableLED.h, U8glib.h, ds3231.h,
Adafruit_Sensor.h, Adafruit_AM2320.h, Adafruit_BMP085_U.h, Adafruit_HMC5883_U.h, Adafruit_GPS.h
После множественных правок, компиляций, отладки (включая плохие контакты — обнаружил, что BMP180 работает без VCC из-за утечки питания по I2C) и загрузки код готов.
Оговорки и ограничения
- Высота рассчитывается по падению давления — работает только в нормальных погодных условиях.
- Компас не имеет программной компенсации наклона — устройство нужно держать горизонтально. На экране фиксированный внешний круг компаса и внутренний переменного радиуса. При наклоне внутренний круг растет. Когда устройство выровнено (X и Y акселерометра ≈ 0) внутренний круг сходится в точку — тогда азимут точен.
- Магнитное склонение зависит от местоположения и вариаций поля Земли (солнечные бури). Без учета склонения азимут будет с ошибкой в несколько градусов. Узнать склонение: https://www.magnetic-declination.com
- Компас чувствителен к близким магнитным объектам (магнитные минералы в горах).
- Минимальное рабочее напряжение ~4.5 В — тогда нужно менять АА-батареи.
- Время и дата прошиваются в коде. Для смены времени нужно извлечь и вставить элемент CR1220, затем загрузить прошивку с новым временем.
- Во время разработки GPS-модуля не было — вшиты демо-координаты. Для повторения нужно подключить GPS-библиотеку и код.
- Плоскость XY акселерометра и магнетометра зависит от монтажа на плате — код требует подстройки.
- Показания акселерометра чувствительны к вибрации — используйте в покое.
Перспективы улучшения
Прошивка:
- Периодическое логирование параметров в EEPROM
- Зуммер при достижении высотных/географических вех
- Напоминание выпить воды, сделать перерыв
- Тревога низкого заряда
- Тревога низкой температуры/влажности
- Программная компенсация наклона компаса (много тригонометрии)
- Автополучение склонения через GPS и IoT-шлюз
Железо:
- Кнопки пользовательского ввода для настройки времени, склонения
- Корпус из 3D-печати
- Литий-полимерный аккумулятор
- Компактная однонапряженная плата для портативности
Заключение
Путешествия и походы круты, а возможность отслеживать изменения среды делает их интереснее. Особенно — наблюдать за высотой, влажностью, давлением и температурой за несколько часов ходьбы. Устройство показывает, на какой высоте начинается высотная болезнь, при какой влажности кожа сохнет. Несмотря на поле для улучшений в схеме и коде, это крутой гаджет для дикой природы!
Внешние ресурсы
https://www.waveshare.com/wiki/Accessory_Shield
Рекомендуемое железо для лучшей версии
Rapid Prototyping Kit
Исходный код
Hiking_Tracker.ino (C/C++)
/*/////////// NOTE ////////// D2 allows soft reset assertable from code D3,D9,D10 are available through 12,13,16 of xbee interface D4, D13 are not accessable ///////////////////////////*/
//// HEADER FILES FOR LIBRARY /////
#include
#include
#include
#include
#include
#include
#include "U8glib.h"
#include "ds3231.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_AM2320.h"
#include
#include
#include
///////////////////////RTC Variables///////////////////
uint8_t time[8];
struct ts t;
int s, m, h, dy, mo, yr;
///////////////// Accelerometer Variables/////////////
float X=0.0, Y=0.0, Z=0.0, Gravity = 0.0;
///////////////// Magnetometer Variables/////////////
float Xm=0.0, Ym=0.0, Zm=0.0;
/////////////////////// RGB LED Variables///////////////
const int rgb_pwr = 12;
const int clk_pin = 6;
const int data_pin = 5;
float hue = 0.0;
boolean up = true;
////////////////////// potentiometer///////////////////
int pot=0;
//////////////////// Batt/Supply Vin /////////////////
float Vbatt =0.0;
//////////////////////Thermometer////////////////////
float temp = 0.0;
///////////////////// Humidity///////////////////////
float humid = 0.0;
////////////////////Air pressure////////////////////
double prsr = 0.0;
////////////////////Altitude ////////////////////
float alt = 0.0;
//////////////// Lat, Long////////////////////
float lat = 0.0, lon = 0.0;
////////////////Compass Heading/////////////////
float heading =0.0;
/////////////////// Other Variables ///////////////
int mstime =0;
int sensor_selector = 1;
//////////// ENUM //I2C Device type Object /////////////////
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0);
TempI2C_LM75 termo = TempI2C_LM75(0x48,TempI2C_LM75::nine_bits);
ADXL345 accelerometer;
ChainableLED leds(clk_pin, data_pin, rgb_pwr, 1);
Adafruit_AM2320 am2320 = Adafruit_AM2320();
Adafruit_BMP085_Unified bmp180 = Adafruit_BMP085_Unified(10085);
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345);
// uncomment to enable GPS (Serial debugging won't be available)
//HardwareSerial mySerial = Serial;
//Adafruit_GPS GPS(&mySerial);
void setup(void) {
// 1.1V Internal Analog Ref
// analogReference(INTERNAL);
// Signals for OLED on Accessory Shield (solder bridges needed for D2,D3,D9,D10)
pinMode(2,1); // self reset D2 to RST
pinMode(3,1); // outs at xbee int 12
pinMode(9,1); // outs at xbee int 13
pinMode(10,1);// outs at xbee int 16
digitalWrite(2,HIGH); digitalWrite(3,HIGH); digitalWrite(9,LOW); digitalWrite(10,HIGH);
pinMode(7,1); pinMode(8,1); digitalWrite(7,HIGH); digitalWrite(8,LOW);
// 10k POT on A0
// Vin Batt/Supply on A1 (3.91k/21.76k)
// Serial Comm for Debuging (optional)
// Serial.begin(9600);
// Accel Init Waiting
if (!accelerometer.begin()) delay(50);
// RTC Interrupt enabling
DS3231_init(DS3231_INTCN);
// Buzzer Init
pinMode(11,1); digitalWrite(11,LOW);
}
void loop(void) {
u8g.firstPage();
do { draw(); } while( u8g.nextPage() );
sensor_selector++;
if (sensor_selector >= 3) sensor_selector = 0;
// Temperature Fetch API
temp = termo.getTemp();
delay(5);
// Pressure Fetch API
if(sensor_selector == 0) {
if(!bmp180.begin()) delay(11);
sensors_event_t event;
bmp180.getEvent(&event);
if (event.pressure) {
prsr = event.pressure/10; // hPa to kPa
delay(50);
}
}
// GPS Init (commented)
//GPS.begin(9600);
// Altitude Fetch API
if(sensor_selector == 0) {
sensors_event_t event;
bmp180.getEvent(&event);
alt = bmp180.pressureToAltitude(1013.25, event.pressure);
delay(50);
}
// Humidity Sensor Init
if(sensor_selector == 1) {
am2320.begin();
delay(50);
humid = am2320.readHumidity();
delay(50);
}
// Acceleration Fetch API
Vector norm = accelerometer.readNormalize();
X = norm.XAxis; Y = norm.YAxis; Z = norm.ZAxis;
Gravity = sqrt(X*X + Y*Y + Z*Z) - 0.3; // offset
// Time Fetch API
DS3231_get(&t);
s = t.sec; m = t.min; h = t.hour; dy = t.mday; mo = t.mon; yr = t.year;
// Pot Position Fetch
pot = analogRead(A0)*5/(10*3);
delay(2);
// VBatt/Supply Fetch
Vbatt = (1.08*analogRead(A1)/1023)/3.91*((3.91+21.76));
delay(2);
// Compass Heading
if(sensor_selector == 2) {
if(!mag.begin()) { while(1); }
delay(70);
sensor_t sensor2;
mag.getSensor(&sensor2);
delay(70);
float declination = 0.0;
sensors_event_t event2;
mag.getEvent(&event2);
heading = atan2(event2.magnetic.y, event2.magnetic.x);
heading += declination;
if(heading < 0) heading += 2*PI;
if(heading > 2*PI) heading -= 2*PI;
heading = heading * 180/M_PI;
}
// Buzzer Control
if (pot > 80) digitalWrite(11,1); else digitalWrite(11,0);
// RGB LED Control
if(pot > 40) {
leds.pwr_set(PWR_ENABLE);
for (byte i=0; i<1; i++) leds.setColorHSB(i, hue, 1.0, 0.5);
if (up) hue += 0.025; else hue -= 0.025;
if (hue >= 1.0 && up) up = false;
else if (hue <= 0.0 && !up) up = true;
}
// Self Reset (commented)
// digitalWrite(2,LOW);
}
void draw(void) {
u8g.setFont(u8g_font_7x13);
u8g.drawHLine(37, 0, 62);
u8g.setPrintPos(41, 12); if(h<10) u8g.print(0); u8g.print(h); u8g.drawStr(54,12,":");
u8g.setPrintPos(62, 12); if(m<10) u8g.print(0); u8g.print(m); u8g.drawStr(75,12,":");
u8g.setPrintPos(83, 12); if(s<10) u8g.print(0); u8g.print(s);
u8g.drawHLine(37, 14, 62);
u8g.setFont(u8g_font_5x8);
u8g.drawVLine(37, 0, 14);
u8g.setPrintPos(0, 7); if(dy<10) u8g.print(0); u8g.print(dy); u8g.drawStr(11,7,"/");
u8g.setPrintPos(17, 7); if(mo<10) u8g.print(0); u8g.print(mo); u8g.drawStr(0,16,"/");
u8g.setPrintPos(6, 16); u8g.print(yr);
u8g.drawVLine(99, 0, 14);
u8g.drawStr(104,7,"BATT");
u8g.setPrintPos(102, 15); u8g.print(Vbatt); u8g.drawStr(123,15,"V");
u8g.print(round(temp)); u8g.drawStr(55,25,"'C");
u8g.setFont(u8g_font_5x8);
u8g.setPrintPos(15, 40); u8g.print(round(X));
u8g.setPrintPos(60, 40); u8g.print(round(Y));
u8g.setPrintPos(100, 40); u8g.print(round(Z));
u8g.drawVLine(37, 18, 36);
u8g.drawStr(39,24,"AIR TEMP: "); u8g.setPrintPos(85,24); u8g.print(temp); u8g.drawStr(118,24,"C");
u8g.drawStr(39,34,"HUMIDITY: "); u8g.setPrintPos(85,34); u8g.print(humid); u8g.drawStr(118,34,"%");
u8g.drawStr(39,44,"PRESSURE: "); u8g.setPrintPos(85,44); u8g.print(prsr); u8g.drawStr(118,44,"hP");
u8g.drawStr(39,54,"ALTITUDE: "); u8g.setPrintPos(85,54); u8g.print(round(alt)); u8g.drawStr(118,54,"m");
u8g.drawHLine(0, 56, 128);
u8g.drawStr(19,64,"LAT:"); u8g.setPrintPos(39,64); u8g.print(23.57); // Demo
u8g.drawStr(69,64,"LONG:"); u8g.setPrintPos(94,64); u8g.print(90.36); // Demo
int r = round(sqrt(X*X + Y*Y));
u8g.drawStr(0,22,"heading");
u8g.drawCircle(18, 34, r, U8G_DRAW_ALL); // inner leveling circle
u8g.drawCircle(18, 34, 10, U8G_DRAW_ALL); // outer fixed circle
u8g.setPrintPos(0, 53); u8g.print(heading);
if ((heading>315)|(heading<=45)) u8g.drawLine(18,34,8,34); // N
if ((heading>45)&(heading<=135)) u8g.drawLine(18,34,18,24); // E
if ((heading>135)&(heading<=225)) u8g.drawLine(18,34,28,34); // S
if ((heading>225)&(heading<315)) u8g.drawLine(18,34,18,44); // W
}
Библиотеки
Распакуйте и добавьте отдельные ZIP-архивы библиотек в Arduino Library Manager.
Схемы

Производственный процесс
- Понимание кислородного (O2) датчика: роль, размещение и признаки отказа
- Управление светодиодом с PIR-датчиком движения на Raspberry Pi
- Построение низкобюджетного датчика влажности почвы на Raspberry Pi для умного полива
- Отслеживание ежедневных упражнений хомяка с Raspberry Pi фитнес-трекером
- Тестирование датчика температуры DS18B20 на Raspberry Pi
- Создайте домашний монитор температуры на Raspberry Pi с MCP9808, InfluxDB и Grafana
- Монитор гигиены туалета на базе Walabot
- Датчик эмоций и ЭЭГ: низкобюджетное железо для детекции эмоций в реальном времени
- Передача данных Adafruit 10DOF IMU с Raspberry Pi 2 в Azure Event Hubs на Windows 10 IoT Core
- Точный WiFi RSSI трекинг для датчиков МКС