main.ino 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. #include "TinyGPSPlus.h"
  2. #include "DHT.h"
  3. #include <Wire.h>
  4. #include <Adafruit_GFX.h>
  5. #include <Adafruit_SSD1306.h>
  6. #include "FS.h"
  7. #include "SD.h"
  8. #include "SPI.h"
  9. #include "Arduino.h"
  10. #include "math.h"
  11. /*
  12. Uncomment and set up if you want to use custom pins for the SPI communication
  13. #define REASSIGN_PINS
  14. int sck = -1;
  15. int miso = -1;
  16. int mosi = -1;
  17. int cs = -1;
  18. */
  19. // Inicia I2C en pines default ESP32 (21 SDA, 22 SCL)
  20. #define SCREEN_WIDTH 128 // Ancho píxeles
  21. #define SCREEN_HEIGHT 64 // Alto píxeles
  22. #define OLED_RESET -1 // Reset pin (no usado)
  23. // Pines para UART2 (Serial2)
  24. #define RX_PIN 16 // RX del ESP32 conectado a TX del GPS
  25. #define TX_PIN 17 // TX del ESP32 conectado a RX del GPS
  26. #define GPS_BAUD 115200
  27. #define DHTPIN 4 // Digital pin connected to the DHT sensor
  28. #define DHTTYPE DHT22
  29. #define BUTTON_PIN 27 // pin para pulsador
  30. //definicion de tiempos de pulsacion
  31. #define PULASCION_LARGA_MS 2000
  32. #define DURACION_WATCHDOG_MS 10000
  33. #define MEASUREMENT_INTERVAL_S 1 //separación entre mediciones (s)
  34. #define DEG2RAD (M_PI/180.0)
  35. // Objeto TinyGPS++
  36. TinyGPSPlus gps;
  37. HardwareSerial gpsSerial(2); // Usar UART2
  38. DHT dht(DHTPIN, DHTTYPE);
  39. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  40. //valores de los sensores
  41. struct SensorData {
  42. double latitude = 0.0;
  43. double longitude = 0.0;
  44. float altura = 0.0;
  45. String tiempo = "";
  46. float temperature = 0.0;
  47. float humidity = 0.0;
  48. float pressure = 0.0;
  49. };
  50. SensorData latestData;
  51. SensorData datosAntiguos;
  52. SemaphoreHandle_t dataMutex; // Mutex para proteger el acceso a latestData
  53. SemaphoreHandle_t buttonSemaphore; // Semáforo para la tarea del botón
  54. bool grabando = false; //inicia apagado
  55. bool finalizado = true; //indica que no hay ninguna grabacion ni iniciada ni pausada
  56. TaskHandle_t medicionesHandle = NULL; //para suspend/resume
  57. int pantallaEstado_grab = -1; //maquina de estados cuando se graba ruta
  58. int pantallaEstado_menu = -1; //maquina de estados cuando no se esta grabando ruta
  59. float distancia_total = 0.0;
  60. volatile unsigned long ignore_isr_until = 0; //para debounce
  61. char filename[13];
  62. void OLED_print(const String& line1, const String& line2) {
  63. display.clearDisplay();
  64. display.setTextSize(2);
  65. display.setTextColor(SSD1306_WHITE);
  66. display.setCursor(0, 0);
  67. display.println(line1);
  68. display.println(line2);
  69. display.display();
  70. }
  71. void DHT_test() {
  72. dht.begin();
  73. float h = dht.readHumidity();
  74. float t = dht.readTemperature();
  75. if (isnan(h) || isnan(t)) {
  76. Serial.println("Failed to read from DHT sensor!");
  77. OLED_print("DHT22", "Error");
  78. delay(5000);
  79. DHT_test(); // Reintentar
  80. }
  81. else OLED_print("DHT22", "Correcto");
  82. }
  83. void OLED_test() { //pantallazo a blanco y luego iniciando
  84. // Inicia I2C en pines default ESP32 (21 SDA, 22 SCL)
  85. Wire.begin();
  86. if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Dirección común: 0x3C
  87. Serial.println(F("Error: OLED no encontrado!"));
  88. for (;;); // Para siempre
  89. }
  90. display.clearDisplay();
  91. display.fillScreen(SSD1306_WHITE); // Pantalla blanca
  92. delay(500);
  93. display.display();
  94. display.clearDisplay();
  95. display.setTextSize(2); // Tamaño texto
  96. display.setTextColor(SSD1306_WHITE);
  97. display.setCursor(0, 0); // Posición
  98. display.println("Iniciando...");
  99. display.display(); // Muestra
  100. delay(1000);
  101. }
  102. void SD_test(){
  103. if (!SD.begin()) {
  104. OLED_print("SD Card", "Error\nInserte");
  105. while (!SD.begin());
  106. OLED_print("SD Card", "Insertada");
  107. } else {
  108. OLED_print("SD Card", "Correcto");
  109. }
  110. uint8_t cardType = SD.cardType();
  111. if (cardType == CARD_NONE) {
  112. OLED_print("SD Card", "No detectada");
  113. while (cardType == CARD_NONE) {
  114. delay(1000);
  115. cardType = SD.cardType();
  116. }
  117. OLED_print("SD Card", "Detectada");
  118. }
  119. uint8_t cardSize = SD.cardSize() / (1024 * 1024);
  120. }
  121. void GPS_test_wait() {
  122. // Iniciar Serial2 para GPS
  123. gpsSerial.begin(GPS_BAUD, SERIAL_8N1, RX_PIN, TX_PIN);
  124. while (((gpsSerial.available() > 0) && gps.location.isValid()) && (gps.speed.age() < 2000)) {
  125. gps.encode(gpsSerial.read());
  126. delay(100);
  127. OLED_print("GPS", "Esperando.");
  128. delay(100);
  129. OLED_print("GPS", "Esperando..");
  130. delay(100);
  131. OLED_print("GPS", "Esperando...");
  132. }
  133. OLED_print("GPS", "Encontrado");
  134. }
  135. float calcular_delta_dist(float lat1, float long1, float lat2, float long2){
  136. float R = 6371.0; // Radio de la Tierra en km
  137. float delta_lat = (lat2 - lat1) * DEG2RAD;
  138. float delta_long = (long2 - long1) * DEG2RAD;
  139. lat1 = lat1 * DEG2RAD;
  140. lat2 = lat2 * DEG2RAD;
  141. float a = sin(delta_lat/2)*sin(delta_lat/2)+cos(lat1)*cos(lat2)*sin(delta_long/2)*sin(delta_long/2);
  142. float c = 2 * atan2(sqrt(a),sqrt(1-a));
  143. return R * c; //En km
  144. }
  145. void task_mediciones(void *pvParameters) {
  146. TickType_t xLastWakeTime = xTaskGetTickCount();
  147. while(1) {
  148. // se leen los valores antes de utilizar el semaphore
  149. while (gpsSerial.available() > 0) {
  150. gps.encode(gpsSerial.read());
  151. }
  152. float new_latitude = gps.location.lat();
  153. float new_longitude = gps.location.lng();
  154. float new_altitude = gps.altitude.meters();
  155. String new_fecha = String(gps.date.year())+"-"+String(gps.date.month())+"-"+
  156. String(gps.date.day())+"T"+String(gps.time.hour())+":"+
  157. String(gps.time.minute())+":"+String(gps.time.second())+"."+
  158. String(gps.time.centisecond());
  159. float new_temp = dht.readTemperature();
  160. float new_hum = dht.readHumidity();
  161. float new_press = 0.0; // Placeholder, no hay sensor de presión
  162. if (gps.location.isValid() && datosAntiguos.latitude != 0.0) {
  163. distancia_total += calcular_delta_dist(datosAntiguos.latitude, datosAntiguos.longitude, new_latitude, new_longitude);
  164. }
  165. if (xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE) {
  166. latestData.latitude = new_latitude;
  167. latestData.longitude = new_longitude;
  168. latestData.altura = new_altitude;
  169. latestData.tiempo = new_fecha;
  170. latestData.temperature = new_temp;
  171. latestData.humidity = new_hum;
  172. latestData.pressure = new_press;
  173. datosAntiguos = latestData;
  174. xSemaphoreGive(dataMutex);
  175. }
  176. File file = SD.open(filename, FILE_APPEND);
  177. if (file) {
  178. //Crear la string para escribir en el archivo
  179. file.print(F("\t\t\t<trkpt lat=\""));
  180. file.print(datosAntiguos.latitude, 6);
  181. file.print(F("\" lon=\""));
  182. file.print(datosAntiguos.longitude, 6);
  183. file.println(F("\">"));
  184. file.print(F("\t\t\t\t<ele>"));
  185. file.print(datosAntiguos.altura);
  186. file.println(F("</ele>"));
  187. file.print(F("\t\t\t\t<time>"));
  188. file.print(datosAntiguos.tiempo);
  189. file.println(F("</time>"));
  190. file.println(F("\t\t\t\t<extensions>"));
  191. file.println(F("\t\t\t\t\t<gpxtpx:TrackPointExtension>"));
  192. file.print(F("\t\t\t\t\t\t<gpxtpx:atemp>"));
  193. file.print(datosAntiguos.temperature);
  194. file.println(F("</gpxtpx:atemp>"));
  195. file.println(F("\t\t\t\t\t</gpxtpx:TrackPointExtension>"));
  196. file.print(F("\t\t\t\t\t<custom:humidity>"));
  197. file.print(datosAntiguos.humidity);
  198. file.println(F("</custom:humidity>"));
  199. file.print(F("\t\t\t\t\t<custom:pressure>"));
  200. file.print(datosAntiguos.pressure);
  201. file.println(F("</custom:pressure>"));
  202. file.println(F("\t\t\t\t</extensions>"));
  203. file.println(F("\t\t\t</trkpt>"));
  204. // Escribir datos en el archivo
  205. file.println(frase);
  206. file.close();
  207. }
  208. vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(MEASUREMENT_INTERVAL_S*1000)); // Espera x*1000 milisegundos
  209. }
  210. }
  211. void crear_archivo(){
  212. int num = 1;
  213. sprintf(filename, "/data%03d.gpx", num);
  214. while (SD.exists(filename)) {
  215. num++;
  216. sprintf(filename, "/data%03d.gpx", num);
  217. }
  218. File file = SD.open(filename, FILE_WRITE);
  219. if (file) {
  220. file.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  221. "<gpx creator=\"ESP32 GPS LOGGER\" version=\"1.1\"\n"
  222. "\txmlns=\"http://www.topografix.com/GPX/1/1\"\n"
  223. "\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
  224. "\txmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v2\"\n"
  225. "\txmlns:gpxdata=\"http://www.cluetrust.com/XML/GPXDATA/1/0\"\n"
  226. "\txsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
  227. "\t<trk>\n"
  228. "\t\t<name>Rutita</name>\n"
  229. "\t\t<type>hiking</type>\n"
  230. "\t\t<trkseg>");
  231. file.close();
  232. } else {
  233. OLED_print("Error","creando archivo");
  234. }
  235. }
  236. void cerrar_archivo() {
  237. File file = SD.open(filename, FILE_APPEND);
  238. if (file){
  239. file.print("\t\t</trkseg>\n\t</trk>\n</gpx>");
  240. file.close();
  241. }
  242. int num = 1;
  243. sprintf(filename, "/data%03d.gpx", num);
  244. while (SD.exists(filename)) {
  245. num++;
  246. sprintf(filename, "/data%03d.gpx", num);
  247. }
  248. }
  249. void IRAM_ATTR isr_button() {
  250. unsigned long now = millis();
  251. if (now < ignore_isr_until) {
  252. return; // Ignorar interrupción si está dentro del período de debounce
  253. }
  254. static unsigned long lastInterrupt = 0;
  255. if ((now - lastInterrupt) > 300 ){ // debounce de 300 ms
  256. BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  257. xSemaphoreGiveFromISR(buttonSemaphore, &xHigherPriorityTaskWoken);
  258. lastInterrupt = now;
  259. if (xHigherPriorityTaskWoken) {
  260. portYIELD_FROM_ISR();
  261. }
  262. }
  263. }
  264. void drawProgressBar(int x, int y, int w, int h, unsigned long progress, unsigned long total) {
  265. display.drawRect(x, y, w, h, SSD1306_WHITE); // Dibuja el borde
  266. int filledWidth = (progress * w) / total;
  267. display.fillRect(x + 1, y + 1, filledWidth - 2, h - 2, SSD1306_WHITE); // Dibuja la barra llena
  268. display.display();
  269. }
  270. void task_ui(void *pvParameters){
  271. unsigned long pressTime = 0;
  272. unsigned long lastActivity = millis();
  273. bool pantallaOn = true; //comprobar el estado inicial, no se cual sera
  274. bool processingButton = false;
  275. while(1){
  276. if (xSemaphoreTake(buttonSemaphore, pdMS_TO_TICKS(200)) == pdTRUE){ //button pressed
  277. if (processingButton) continue; //evita reentradas
  278. processingButton = true;
  279. pressTime = millis();
  280. lastActivity = millis(); //reset watchdog
  281. if (!pantallaOn){
  282. display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  283. pantallaOn = true;
  284. }
  285. bool timed_out = false;
  286. unsigned long checkTime = millis();
  287. while (digitalRead(BUTTON_PIN) == LOW){
  288. vTaskDelay(pdMS_TO_TICKS(10));
  289. checkTime = millis();
  290. if ((checkTime - pressTime) > DURACION_WATCHDOG_MS){ //10s timeout para evitar bloqueos
  291. timed_out = true;
  292. break;
  293. }
  294. drawProgressBar(0, SCREEN_HEIGHT - 10, SCREEN_WIDTH, 8, checkTime - pressTime, PULASCION_LARGA_MS);
  295. }
  296. ignore_isr_until = millis() + 500; //ignorar nuevas interrupciones durante 500 ms
  297. unsigned long duration = checkTime - pressTime;
  298. if (timed_out){
  299. OLED_print("Apagando","pantalla");
  300. display.ssd1306_command(SSD1306_DISPLAYOFF); //se apaga la
  301. pantallaOn = false;
  302. } else {
  303. if (grabando){
  304. if (duration >= PULASCION_LARGA_MS){
  305. grabando = false;
  306. vTaskSuspend(medicionesHandle);
  307. OLED_print("Ruta","pausada");
  308. } else {
  309. pantallaEstado_grab = (pantallaEstado_grab + 1) % 5; //cicla entre 0-4
  310. SensorData currentData;
  311. if(xSemaphoreTake(dataMutex, portMAX_DELAY) == pdTRUE){
  312. currentData = latestData;
  313. xSemaphoreGive(dataMutex);
  314. }
  315. switch (pantallaEstado_grab){
  316. case 0:
  317. OLED_print("Posicion",String(currentData.longitude) + "," + String(currentData.latitude));
  318. break;
  319. case 1:
  320. OLED_print("Distancia",String(distancia_total)+"km");
  321. break;
  322. case 2:
  323. OLED_print("Altitud",String(gps.altitude.meters(), 1)+"m");
  324. break;
  325. case 3:
  326. OLED_print("Temp/Hum",String(currentData.temperature,1)+"C/"+String(currentData.humidity,1)+"%");
  327. break;
  328. case 4:
  329. OLED_print("Velocidad",String(gps.speed.kmph())+"km/h");
  330. break;
  331. }
  332. }
  333. } else {
  334. if (duration >= PULASCION_LARGA_MS){
  335. switch (pantallaEstado_menu){
  336. case 0:
  337. //activar la ruta y crear el archivo
  338. crear_archivo();
  339. vTaskResume(medicionesHandle);
  340. OLED_print("Ruta","iniciada");
  341. finalizado = false;
  342. break;
  343. case 1:
  344. //cerrar el archivo y cambiar el valor de 'filename'
  345. cerrar_archivo();
  346. finalizado = true;
  347. break;
  348. case 2:
  349. //implementacion blutuch
  350. break;
  351. }
  352. } else {
  353. pantallaEstado_menu = (pantallaEstado_menu + 1) % 3;
  354. switch (pantallaEstado_menu){
  355. case 0:
  356. if (!finalizado) OLED_print("Reanudar","ruta");
  357. else OLED_print("Iniciar","ruta");
  358. break;
  359. case 1:
  360. if (SD.exists(filename)) {
  361. OLED_print("Finalizar","ruta");
  362. break;
  363. }
  364. pantallaEstado_menu += 1;
  365. case 2:
  366. if (finalizado) {
  367. OLED_print("Conexion","blutuch 'WIP'");
  368. break;
  369. }
  370. pantallaEstado_menu += 1;
  371. }
  372. }
  373. }
  374. }
  375. lastActivity = millis(); //reset watchdog
  376. processingButton = false;
  377. vTaskDelay(pdMS_TO_TICKS(100)); //pequeño delay para no busy waiting
  378. }
  379. //check watchdog fuera del boton
  380. if (pantallaOn && ((millis() - lastActivity) > DURACION_WATCHDOG_MS)){
  381. display.ssd1306_command(SSD1306_DISPLAYOFF); //se apaga la pantalla
  382. pantallaOn = false;
  383. }
  384. }
  385. }
  386. void setup() {
  387. pinMode(BUTTON_PIN, INPUT_PULLUP);
  388. attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), isr_button, FALLING);
  389. buttonSemaphore = xSemaphoreCreateBinary();
  390. dataMutex = xSemaphoreCreateMutex();
  391. // OLED check
  392. OLED_test();
  393. delay(1000);
  394. // DHT check
  395. DHT_test();
  396. delay(1000);
  397. // SD Card check
  398. SD_test();
  399. delay(1000);
  400. // GPS check
  401. GPS_test_wait();
  402. delay(2000);
  403. // Crear tarea para mediciones
  404. xTaskCreatePinnedToCore(
  405. task_mediciones, // Función de la tarea
  406. "Mediciones", // Nombre de la tarea
  407. 8192, // Tamaño del stack
  408. NULL, // Parámetro de la tarea
  409. 10, // Prioridad de la tarea
  410. &medicionesHandle, // Handle de la tarea
  411. 0 // Núcleo donde se ejecuta
  412. );
  413. xTaskCreatePinnedToCore(
  414. task_ui, // Función de la tarea
  415. "UI", // Nombre de la tarea
  416. 8192, // Tamaño del stack
  417. NULL, // Parámetro de la tarea
  418. 5, // Prioridad de la tarea
  419. NULL, // Handle de la tarea
  420. 1 // Núcleo donde se ejecuta
  421. );
  422. vTaskSuspend(medicionesHandle); //inicia suspendida
  423. }
  424. void loop() {
  425. vTaskDelay(pdMS_TO_TICKS(1000)); // Espera 1 segundo
  426. }