Rastreador de senderismo avanzado con Arduino: GPS, sensores y batería
Componentes y suministros
| Arduino 101 | × | 1 |
| Pilas AA + Portapilas 4×AA | × | 1 |
| Accessory Shield (para Arduino) | × | 1 |
| Sensor de presión BMP180 (Adafruit) | × | 1 |
| GPS Ultimate Breakout (Adafruit) | × | 1 |
| Sensor magnético 3 ejes (Adafruit HMC5883) | × | 1 |
| Sensor de humedad AM2320 (Adafruit) | × | 1 |
| Pilas botón CR2012 (para RTC) | × | 2 |
| Resistencias 10 kΩ y 3.9 kΩ | × | 1 c/u |
Herramientas y máquinas necesarias
- Pistola de silicona caliente
- Soldador
Aplicaciones y servicios en línea
- Arduino IDE
Sobre este proyecto
¿Qué es Hiking Tracker?
El Hiking Tracker es un dispositivo para detectar cambios en el entorno del senderista durante la caminata. La idea es observar variaciones de temperatura, presión, humedad, altitud, ubicación, dirección, etc. Ofrece información interesante sobre la ruta.
En montaña, donde hace frío, aire seco y baja densidad, cuesta respirar y algunos sufren irritación cutánea. Este dispositivo permite a los aficionados conocer sus límites. También está la diversión de alcanzar gran altitud; ¡poder saber la altitud es divertido!
No solo montañas: explorar pantanos, desiertos, bosques – cada uno con su perfil ambiental único – monitorizar el entorno en marcha ayuda a entender mejor la naturaleza.

Paso 1: Hardware – ¿qué para qué?
Hay mucha sensorización en este proyecto.
El Accessory Shield trae dispositivos I2C a bordo:
- LM75B – temperatura ambiente
- ADXL345 – acelerómetro 3 ejes (inclinación y gravedad)
- DS3231 – RTC de alta precisión (hora)
- HMC5883 – magnetómetro 3 ejes (brújula)
- AM2320 – humedad relativa
- BMP180 – presión atmosférica y altitud
- MTK3339 GPS – ubicación y distancia recorrida
En el Arduino Uno (shield):
- Divisor de tensión 3.9 kΩ + 22 kΩ para medir voltaje de 4 pilas AA
- OLED 1306 para visualizar datos
- Buzzer para alarma
- LED RGB (no usado), interfaz XBee (no usada pero expone D2, D3, D9, D10; D2 conectado a reset permite reset por software)
- Joystick 5-vías y potenciómetro 101 (no usados)
Paso 2: Modificando y conectando el hardware
Pocas modificaciones en el Arduino Uno. Se monta sobre el portapilas 4×AA con separadores roscados y silicona caliente.


Se instalan 4 pilas AA y CR1220 (para RTC) en sus portapilas.


Hay espacio entre el portapilas y la placa Arduino donde caben sensores de presión, humedad, magnético y GPS.


Los sensores I2C se sueldan juntos en una placa de pruebas y se colocan en ese espacio.


Estos sensores se conectan al Arduino desde la cara inferior:

Se añade un divisor de tensión para medir el voltaje de las 4 pilas AA.

La línea de medición de voltaje va al ADC A1:

El joystick del shield está mapeado al pin A1 a través de A5. Por eso se desuelda y retira.

Conectando pads en el Accessory Shield (ver esquema en referencias) con puentes de soldadura, se hacen disponibles D2 (reset suave), D3, D9, D10 en la interfaz XBee.


La pila de componentes queda así:
- Capa superior: Accessory Shield sobre Arduino
- Capa media: Otros sensores y GPS entre Uno y portapilas
- Capa inferior: Portapilas 4×AA con pilas
Y las conexiones son así:

Paso 3: Programando el dispositivo
Se usa Arduino IDE Build 1.8.5. Primero se incluyen/descargan todas las librerías necesarias mediante el gestor de librerías.

Escribiendo el nombre del sensor en la búsqueda aparecen las librerías adecuadas.

Algunas librerías del Accessory Shield (ver referencias) se añaden vía archivo .zip.

Tras añadir librerías, se revisan los ejemplos de cada sensor para conocer sus APIs.
Luego se incluyen todos los headers en un sketch vacío.
Lista de headers
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
Tras múltiples ediciones, compilaciones, depuración (incluyendo conexiones sueltas, donde descubrí que el BMP180 funciona sin Vcc por fuga de potencia en pines I2C) y subidas, el código quedó listo.
Las advertencias (caveats)
- El cálculo de altitud se basa en caída de presión, válido solo en condiciones meteorológicas normales.
- El código de brújula no está compensado por inclinación en software; el dispositivo debe sostenerse en plano horizontal. Hay un círculo fijo y otro variable; al inclinar, el interior crece. Cuando está nivelado (componentes X e Y del acelerómetro ≈ 0), el círculo interior se reduce a un punto: entonces el rumbo es más preciso.
- El ángulo de declinación magnética depende de la ubicación y variación del campo terrestre (puede verse afectado por tormentas solares). Sin declinación, el rumbo falla unos grados. Busca la tuya en: https://www.magnetic-declination.com
- El rumbo es sensible a objetos magnéticos cercanos (minerales fuertes en montañas).
- Voltaje mínimo de operación ~4.5 V; entonces hay que cambiar las pilas AA.
- Hora y fecha se programan en el código; para cambiarlas hay que quitar y poner la pila botón y subir código nuevo con la hora actualizada.
- Durante el desarrollo no tenía módulo GPS; puse coordenadas demo en Lat/Lon. Para replicar, hay que incluir librería GPS y código asociado.
- El plano XY de acelerómetro y magnetómetro depende de su ubicación en PCB; el código debe ajustarse en consecuencia.
- La precisión del acelerómetro sufre con vibraciones; usar en reposo.
Alcance de mejoras
Mejoras en firmware para más funciones:
- Registro periódico de parámetros en EEPROM
- Alarma buzzer al alcanzar hitos de altitud/ubicación
- Recordatorio beber agua / descanso
- Alarma batería baja
- Alertas temperatura/humedad baja
- Calibración software brújula para compensación inclinación (trigonometría)
- Obtención automática de declinación vía GPS e IoT mediante app Gateway
Mejoras en hardware:
- Interruptores de usuario para ajustar hora, declinación, etc.
- Carcasa 3D personalizada
- Batería LiPo recargable
- PCB compacta de una sola placa para mayor portabilidad
Conclusión
Viajar y hacer senderismo es genial, y poder consultar los cambios del entorno lo hace más interesante. Especialmente ver altitud, humedad, presión y temperatura cambiar en horas de caminata. Este dispositivo te dice a qué altura sientes mal de altura, qué humedad reseca la piel, etc. Aunque hay margen de mejora en circuito y código para resolver las advertencias, ¡es genial tener un gadget así en la naturaleza!
Recursos externos
https://www.waveshare.com/wiki/Accessory_Shield
Hardware recomendado para una versión mejor
Kit de prototipado rápido
Código
Hiking_Tracker.ino
/*/////////// NOTE ///////////
D2 permite soft reset desde código
D3,D9,D10 disponibles en interfaz XBee (pines 12,13,16)
D4, D13 no accesibles
///////////////////////////*/
//// HEADER FILES FOR LIBRARY /////
#include <math.h>
#include <inttypes.h>
#include <Wire.h>
#include <lm75.h>
#include <ADXL345.h>
#include <ChainableLED.h>
#include "U8glib.h"
#include "ds3231.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_AM2320.h"
#include <Adafruit_BMP085_U.h>
#include <Adafruit_HMC5883_U.h>
#include <Adafruit_GPS.h>
///////////////////// 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); // OLED I2C
TempI2C_LM75 termo = TempI2C_LM75(0x48, TempI2C_LM75::nine_bits); // Temp I2C
ADXL345 accelerometer; // Accel I2C
ChainableLED leds(clk_pin, data_pin, rgb_pwr, 1); // RGB LED I2C
Adafruit_AM2320 am2320 = Adafruit_AM2320(); // Humid I2C
Adafruit_BMP085_Unified bmp180 = Adafruit_BMP085_Unified(10085); // Press I2C
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345); // Compass I2C
// Descomenta las 2 líneas siguientes para activar GPS
// Serial Debugging no estará disponible
// HardwareSerial mySerial = Serial;
// Adafruit_GPS GPS(&mySerial);
void setup(void) {
// 1.1V Internal Analog Ref (opcional)
// analogReference(INTERNAL);
// Señales para OLED en Accessory Shield
// Puentes de soldadura en shield necesarios para D2,D3,D9,D10
pinMode(2, OUTPUT); // self reset D2 a RST
pinMode(3, OUTPUT); // salida en XBee int 12
pinMode(9, OUTPUT); // salida en XBee int 13
pinMode(10, OUTPUT); // salida en XBee int 16
digitalWrite(2, HIGH);
digitalWrite(3, HIGH);
digitalWrite(9, LOW);
digitalWrite(10, HIGH);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
digitalWrite(7, HIGH);
digitalWrite(8, LOW);
// 10k POT en A0
// Vin Batt/Supply en A1 (3.91k/21.76k)
// Serial Comm para Debug (opcional)
// Serial.begin(9600);
// Accel Init Waiting
if (!accelerometer.begin()) delay(50);
// RTC Interrupt enabling
DS3231_init(DS3231_INTCN);
// Buzzer Init
pinMode(11, OUTPUT);
digitalWrite(11, LOW);
}
void loop(void) {
// Picture loop
u8g.firstPage();
do { draw(); } while (u8g.nextPage());
// Switch entre sensores I2C
sensor_selector++;
if (sensor_selector >= 3) sensor_selector = 0;
////// Temperature Fetch API //////
temp = termo.getTemp();
delay(5); // °C
/////// 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 a kPa
delay(50);
}
}
////////////// GPS Init //////////
// 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); // metros
// bmp180.end();
}
////////////// Humidity Sensor Init ///////
if (sensor_selector == 1) {
am2320.begin();
delay(50);
humid = am2320.readHumidity();
delay(50); // % RH
// am2320.end();
}
////// 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
// m/s^2
///// 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 //////
// 1.1 es 1.08 para este chip
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);
// Ángulo declinación depende de geolocalización
// Usar Lat/Lon GPS para calcular
sensors_event_t event2;
mag.getEvent(&event2);
delay(70);
float declination = 0.0;
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; // rad a grados
// mag.end();
}
///// Buzzer Control //////////
if (pot > 80) digitalWrite(11, HIGH); else digitalWrite(11, LOW);
///// RGB Addressable 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 ///////////
// 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.setPrintPos(95, 22); u8g.print(Vbatt); u8g.drawStr(123, 22, "V");
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
u8g.print(round(temp)); u8g.drawStr(55, 25, "'C");
int r = round(sqrt(X*X + Y*Y));
u8g.drawStr(0, 22, "heading");
u8g.drawCircle(18, 34, r, U8G_DRAW_ALL); // círculo interior nivelación
u8g.drawCircle(18, 34, 10, U8G_DRAW_ALL); // círculo exterior fijo
u8g.setPrintPos(0, 53); u8g.print(heading); // 0/360=N, 90=E, 180=S, 270=W
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
}
Librerías
Descomprime y añade los .zip individuales al Gestor de Librerías de Arduino.
Esquemáticos

Proceso de manufactura
- Controle un LED con Sensor PIR de Movimiento en Raspberry Pi
- Construya un Sensor de Humedad de Suelo de Bajo Coste para Riego Inteligente con Raspberry Pi
- Registre el Ejercicio Diario de su Hámster con un Rastreador de Actividad Raspberry Pi
- Prueba del Sensor de Temperatura DS18B20 en Raspberry Pi
- Monitor de temperatura casero con Raspberry Pi, MCP9808, InfluxDB y Grafana
- Monitor de higiene de inodoro con Walabot
- Sensor de Emociones y EEG: Hardware de Bajo Coste para Detección de Emociones en Tiempo Real
- Envío de Datos IMU Adafruit 10DOF desde Raspberry Pi 2 a Azure Event Hubs con Windows 10 IoT Core
- UnifiedWater v1 – Kit completo de sensor de calidad de agua con Arduino
- Entendiendo el sensor de oxígeno (O2): función, ubicación y señales de fallo
- Seguimiento preciso de RSSI WiFi para sensores de la ISS