Created
October 3, 2025 11:02
-
-
Save IgorDePaula/bee85231b162c5bbe4a023449e355f07 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <SPI.h> | |
| #include <SD.h> | |
| #include <Wire.h> | |
| #include <SSD1306.h> | |
| #include <RTClib.h> | |
| #include <BLEDevice.h> | |
| #include <BLEServer.h> | |
| #include <BLEUtils.h> | |
| #include <BLE2902.h> | |
| #include "LoRa.h" | |
| // Configuração do Display OLED | |
| #define OLED_SDA 4 | |
| #define OLED_SCL 15 | |
| #define OLED_RST 16 | |
| SSD1306 display(0x3c, OLED_SDA, OLED_SCL); | |
| #define BAND 915E6 // Frequência (915MHz para América do Sul) | |
| #define SS 18 // GPIO18 -- SX1278's CS | |
| #define RST 14 // GPIO14 -- SX1278's RESET | |
| #define DI0 26 // GPIO26 -- SX1278's IRQ(Interrupt Request) | |
| int packetCount = 0; | |
| int lastSendTime = 0; | |
| int interval = 2000; // Intervalo entre envios (2 segundos) | |
| String lastReceived = ""; | |
| int lastRSSI = 0; | |
| // Configuração do SD Card - Pinos seguros | |
| #define SD_CS 5 // Chip Select (CS) | |
| #define SD_MOSI 23 // Master Out Slave In (MOSI) | |
| #define SD_MISO 2 // Master In Slave Out (MISO) | |
| #define SD_SCK 13 // Serial Clock (SCK) | |
| // RTC DS3231 (usa I2C - SDA=21, SCL=22 por padrão) | |
| // ATENÇÃO: Como SDA/SCL do display já usam 4/15, | |
| // vamos usar I2C secundário nos pinos 21/22 | |
| RTC_DS3231 rtc; | |
| SPIClass sdSPI(VSPI); | |
| bool sdOk = false; | |
| bool rtcOk = false; | |
| void setup() { | |
| Serial.begin(115200); | |
| delay(2000); | |
| Serial.println("=== HELTEC ESP32 + SD CARD + RTC ==="); | |
| // Inicializa display | |
| inicializaDisplay(); | |
| // Inicializa I2C secundário para RTC (pinos 21, 22) | |
| Wire1.begin(21, 22); // SDA=21, SCL=22 | |
| // Inicializa RTC | |
| inicializaRTC(); | |
| // Inicializa SD Card | |
| inicializaSD(); | |
| inicializaLoRa(); | |
| // Se tudo OK, grava dados iniciais | |
| if (sdOk && rtcOk) { | |
| gravaLogInicial(); | |
| } | |
| atualizaDisplay(); | |
| } | |
| void inicializaLoRa() { | |
| LoRa.setPins(SS, RST, DI0); | |
| // Inicializa LoRa | |
| if (!LoRa.begin(BAND)) { | |
| Serial.println("Erro ao iniciar LoRa!"); | |
| display.drawString(0,0,"Erro Lora!");//displayMessage("ERRO LoRa!", "Verifique", "conexões"); | |
| while (1) | |
| ; | |
| } | |
| // Configurações LoRa | |
| LoRa.setSpreadingFactor(7); // SF7 - SF12 (maior = mais alcance, menor velocidade) | |
| LoRa.setSignalBandwidth(125E3); // 125kHz | |
| LoRa.setCodingRate4(5); // 4/5 | |
| LoRa.setPreambleLength(8); // Preâmbulo | |
| LoRa.enableCrc(); | |
| Serial.println("LoRa inicializado com sucesso!"); | |
| Serial.println("Frequência: " + String(BAND / 1E6) + " MHz"); | |
| //displayMessage("LoRa OK!", "915 MHz", "Aguardando..."); | |
| } | |
| void loop() { | |
| static unsigned long ultimaGravacao = 0; | |
| // Grava dados a cada 10 segundos | |
| if (millis() - ultimaGravacao > 10000) { | |
| ultimaGravacao = millis(); | |
| if (sdOk && rtcOk) { | |
| gravaDadosComTimestamp(); | |
| } | |
| } | |
| int packetSize = LoRa.parsePacket(); | |
| if (packetSize) { | |
| receiveMessage(); | |
| } | |
| atualizaDisplay(); | |
| delay(1000); | |
| } | |
| void receiveMessage() { | |
| String message = ""; | |
| // Lê mensagem recebida | |
| while (LoRa.available()) { | |
| message += (char)LoRa.read(); | |
| } | |
| int rssi = LoRa.packetRssi(); | |
| float snr = LoRa.packetSnr(); | |
| lastReceived = message; | |
| lastRSSI = rssi; | |
| // Debug serial | |
| Serial.println("========================================"); | |
| Serial.println("Mensagem recebida: " + message); | |
| Serial.println("RSSI: " + String(rssi) + " dBm"); | |
| Serial.println("SNR: " + String(snr) + " dB"); | |
| Serial.println("========================================"); | |
| // Atualiza display | |
| //displayMessage("RECEBIDO", message, "RSSI: " + String(rssi) + "dBm"); | |
| } | |
| void sendMessage() { | |
| packetCount++; | |
| //String state = voltagemMean < 30 ? "alto" : "baixo"; | |
| //String message = "Boia em nivel " + state; | |
| // Envia mensagem | |
| //LoRa.beginPacket(); | |
| //LoRa.print(voltagemMean < 30 ? 1 : 0); | |
| //LoRa.endPacket(); | |
| //Serial.println("Enviado: " + message); | |
| //displayMessage("ENVIANDO", message, ""); | |
| } | |
| void inicializaDisplay() { | |
| pinMode(OLED_RST, OUTPUT); | |
| digitalWrite(OLED_RST, LOW); | |
| delay(20); | |
| digitalWrite(OLED_RST, HIGH); | |
| display.init(); | |
| display.flipScreenVertically(); | |
| display.clear(); | |
| display.setFont(ArialMT_Plain_10); | |
| display.drawString(0, 0, "Inicializando..."); | |
| display.display(); | |
| Serial.println("Display OK"); | |
| } | |
| void inicializaRTC() { | |
| Serial.print("Inicializando RTC DS3231... "); | |
| if (!rtc.begin(&Wire1)) { | |
| Serial.println("RTC não encontrado!"); | |
| rtcOk = false; | |
| return; | |
| } | |
| rtcOk = true; | |
| Serial.println("OK"); | |
| // Verifica se RTC perdeu energia | |
| if (rtc.lostPower()) { | |
| Serial.println("RTC perdeu energia, configurando data/hora..."); | |
| // Configura data/hora atual (ajuste conforme necessário) | |
| rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); | |
| } | |
| // Mostra data/hora atual | |
| DateTime now = rtc.now(); | |
| Serial.printf("Data/Hora atual: %02d/%02d/%04d %02d:%02d:%02d\n", | |
| now.day(), now.month(), now.year(), | |
| now.hour(), now.minute(), now.second()); | |
| // Configurações do DS3231 | |
| rtc.disable32K(); // Desabilita saída 32kHz | |
| rtc.clearAlarm(1); // Limpa alarme 1 | |
| rtc.clearAlarm(2); // Limpa alarme 2 | |
| rtc.writeSqwPinMode(DS3231_OFF); // Desabilita onda quadrada | |
| } | |
| void inicializaSD() { | |
| Serial.print("Inicializando SD Card... "); | |
| // Configura SPI customizado | |
| sdSPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); | |
| if (!SD.begin(SD_CS, sdSPI)) { | |
| Serial.println("FALHOU!"); | |
| sdOk = false; | |
| return; | |
| } | |
| sdOk = true; | |
| Serial.println("OK"); | |
| // Mostra informações do cartão | |
| uint64_t cardSize = SD.cardSize() / (1024 * 1024); | |
| Serial.printf("Tamanho do cartão: %llu MB\n", cardSize); | |
| Serial.printf("Espaço usado: %llu MB\n", SD.usedBytes() / (1024 * 1024)); | |
| } | |
| void gravaLogInicial() { | |
| DateTime now = rtc.now(); | |
| File arquivo = SD.open("/system_log.txt", FILE_APPEND); | |
| if (!arquivo) { | |
| Serial.println("Erro ao abrir log do sistema"); | |
| return; | |
| } | |
| arquivo.println("==============================="); | |
| arquivo.printf("SISTEMA INICIADO: %02d/%02d/%04d %02d:%02d:%02d\n", | |
| now.day(), now.month(), now.year(), | |
| now.hour(), now.minute(), now.second()); | |
| arquivo.printf("ESP32 ID: %08X\n", (uint32_t)ESP.getEfuseMac()); | |
| arquivo.printf("RAM Livre: %d KB\n", ESP.getFreeHeap() / 1024); | |
| arquivo.printf("SD Card: %llu MB\n", SD.cardSize() / (1024 * 1024)); | |
| arquivo.println("Status: RTC + SD funcionando"); | |
| arquivo.println("==============================="); | |
| arquivo.close(); | |
| Serial.println("Log inicial gravado com sucesso!"); | |
| } | |
| void gravaDadosComTimestamp() { | |
| DateTime now = rtc.now(); | |
| // Nome do arquivo baseado na data | |
| char nomeArquivo[32]; | |
| sprintf(nomeArquivo, "/dados_%04d_%02d_%02d.csv", | |
| now.year(), now.month(), now.day()); | |
| // Verifica se é um arquivo novo (precisa de cabeçalho) | |
| bool arquivoNovo = !SD.exists(nomeArquivo); | |
| File arquivo = SD.open(nomeArquivo, FILE_APPEND); | |
| if (!arquivo) { | |
| Serial.println("Erro ao abrir arquivo de dados"); | |
| return; | |
| } | |
| // Adiciona cabeçalho se arquivo novo | |
| if (arquivoNovo) { | |
| arquivo.println("timestamp,data,hora,temperatura,umidade,bateria,ram_livre"); | |
| } | |
| // Simula dados de sensores | |
| float temperatura = 20.0 + random(0, 150) / 10.0; // 20.0 a 35.0°C | |
| float umidade = 40.0 + random(0, 400) / 10.0; // 40.0 a 80.0% | |
| int bateria = random(70, 101); // 70% a 100% | |
| int ramLivre = ESP.getFreeHeap() / 1024; // KB | |
| // Grava dados | |
| arquivo.printf("%lu,%02d/%02d/%04d,%02d:%02d:%02d,%.1f,%.1f,%d,%d\n", | |
| now.unixtime(), | |
| now.day(), now.month(), now.year(), | |
| now.hour(), now.minute(), now.second(), | |
| temperatura, umidade, bateria, ramLivre); | |
| arquivo.close(); | |
| Serial.printf("Dados gravados: %02d/%02d/%04d %02d:%02d:%02d - T:%.1f°C, U:%.1f%%, B:%d%%\n", | |
| now.day(), now.month(), now.year(), | |
| now.hour(), now.minute(), now.second(), | |
| temperatura, umidade, bateria); | |
| } | |
| void atualizaDisplay() { | |
| display.clear(); | |
| // Status dos módulos | |
| display.drawString(0, 0, "Status Sistema:"); | |
| display.drawString(0, 12, rtcOk ? "RTC: OK ✓" : "RTC: ERRO ✗"); | |
| display.drawString(64, 12, sdOk ? "SD: OK ✓" : "SD: ERRO ✗"); | |
| // Se RTC OK, mostra data/hora | |
| if (rtcOk) { | |
| DateTime now = rtc.now(); | |
| char dataHora[32]; | |
| sprintf(dataHora, "%02d/%02d/%04d %02d:%02d:%02d", | |
| now.day(), now.month(), now.year(), | |
| now.hour(), now.minute(), now.second()); | |
| display.drawString(0, 26, dataHora); | |
| } | |
| // Informações do sistema | |
| display.drawString(0, 38, "RAM: " + String(ESP.getFreeHeap() / 1024) + " KB"); | |
| display.drawString(0, 50, "Tempo: " + String(millis() / 1000) + "s"); | |
| display.display(); | |
| } | |
| // Funções auxiliares para ajustar RTC manualmente | |
| void ajustarRTC(int ano, int mes, int dia, int hora, int minuto, int segundo) { | |
| rtc.adjust(DateTime(ano, mes, dia, hora, minuto, segundo)); | |
| Serial.printf("RTC ajustado para: %02d/%02d/%04d %02d:%02d:%02d\n", | |
| dia, mes, ano, hora, minuto, segundo); | |
| } | |
| // Função para listar arquivos do SD | |
| void listarArquivos() { | |
| if (!sdOk) return; | |
| Serial.println("\n--- ARQUIVOS NO SD CARD ---"); | |
| File root = SD.open("/"); | |
| File arquivo = root.openNextFile(); | |
| while (arquivo) { | |
| if (!arquivo.isDirectory()) { | |
| Serial.printf("%-20s %8d bytes\n", arquivo.name(), arquivo.size()); | |
| } | |
| arquivo = root.openNextFile(); | |
| } | |
| Serial.printf("Espaço total: %llu MB\n", SD.totalBytes() / (1024 * 1024)); | |
| Serial.printf("Espaço usado: %llu MB\n", SD.usedBytes() / (1024 * 1024)); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment