/* ================================================================ BME280 - TEMPERATURA, UMIDITA' E PRESSIONE ================================================================ Piattaforma: - ATmega328P, ad esempio Arduino UNO R3 Sensore: - Bosch BME280 Comunicazione: - I2C Collegamenti I2C su ATmega328P / Arduino UNO R3: - SDA -> A4 - SCL -> A5 - GND -> GND - Alimentazione -> secondo il modulo BME280 utilizzato ATTENZIONE: Il chip BME280 lavora a bassa tensione. Alcune breakout board dispongono di regolatore e adattamento dei livelli logici, altre no. Prima di collegare VCC verificare sempre il modulo utilizzato. Librerie necessarie: - Adafruit BME280 Library - Adafruit Unified Sensor Installabili dal Library Manager dell'Arduino IDE. Il programma: 1. avvia il Serial Monitor a 115200 bit/s; 2. avvia il bus I2C; 3. cerca automaticamente il BME280 prima a 0x76 e poi a 0x77; 4. mostra una sola volta le informazioni di inizializzazione; 5. esegue una prima lettura subito dopo l'avvio; 6. successivamente legge temperatura, umidita' e pressione ogni 60 secondi; 7. mostra le misure in un formato ordinato; 8. controlla che i valori restituiti siano plausibili; 9. se il sensore non viene trovato all'avvio, riprova periodicamente senza bloccare il programma. Lo sketch non usa delay() per gestire la temporizzazione periodica delle letture. ================================================================ */ // --------------------------------------------------------------- // LIBRERIE // --------------------------------------------------------------- // Wire gestisce il bus I2C dell'ATmega328P. #include // Libreria comune richiesta dalle librerie sensori Adafruit. #include // Libreria specifica per il BME280. #include // --------------------------------------------------------------- // OGGETTO BME280 // --------------------------------------------------------------- // Questo oggetto rappresenta il sensore BME280. // // Attraverso "bme" possiamo: // - inizializzare il dispositivo; // - leggere temperatura; // - leggere umidita'; // - leggere pressione; // - ottenere l'identificativo del chip. Adafruit_BME280 bme; // --------------------------------------------------------------- // TEMPORIZZAZIONI // --------------------------------------------------------------- // Intervallo tra due acquisizioni successive. // // 60000 ms = 60 secondi. // // Temperatura, umidita' e pressione ambientali cambiano // relativamente lentamente. Una lettura al minuto permette // di osservare chiaramente l'andamento senza riempire inutilmente // il Serial Monitor con decine di valori quasi identici. const unsigned long INTERVALLO_LETTURA_MS = 60000UL; // Se il BME280 non viene trovato, il programma non si blocca. // // Ogni 5 secondi viene effettuato un nuovo tentativo di // inizializzazione. Questo permette, per esempio, di correggere // un collegamento errato senza dover necessariamente premere RESET. const unsigned long INTERVALLO_RICERCA_MS = 5000UL; // Memorizza quando e' stata eseguita l'ultima acquisizione. unsigned long ultimaLetturaMs = 0; // Memorizza quando e' stato effettuato l'ultimo tentativo // di ricerca del sensore. unsigned long ultimaRicercaMs = 0; // --------------------------------------------------------------- // STATO DEL SENSORE // --------------------------------------------------------------- // Diventa true soltanto dopo che il BME280 e' stato trovato // e inizializzato correttamente. bool sensoreDisponibile = false; // Qui viene memorizzato l'indirizzo I2C realmente utilizzato. // // Il BME280 puo' normalmente rispondere a: // - 0x76 // - 0x77 uint8_t indirizzoBME280 = 0; // Contatore progressivo delle acquisizioni. // // Serve esclusivamente per rendere piu' leggibile il Serial Monitor: // ogni blocco di dati ha un numero progressivo. unsigned long numeroMisura = 0; // =============================================================== // SETUP // =============================================================== void setup() { // ------------------------------------------------------------- // SERIAL MONITOR // ------------------------------------------------------------- // Avvia la porta seriale a 115200 bit/s. // // In questo programma il Serial Monitor viene utilizzato anche // come strumento diagnostico. All'avvio mostra: // - stato del bus I2C; // - presenza o assenza del BME280; // - indirizzo I2C rilevato; // - identificativo del chip. // // Durante il funzionamento mostra invece, una volta al minuto: // - numero progressivo della misura; // - tempo trascorso dall'avvio; // - temperatura; // - umidita'; // - pressione; // - eventuali errori di lettura. // // 115200 bit/s permette di trasferire rapidamente queste // informazioni e riduce il tempo impiegato dalle numerose // istruzioni Serial.print(). // // Nel Serial Monitor dell'Arduino IDE deve essere selezionata // la stessa velocita': 115200 baud. Serial.begin(115200); // Le stringhe costanti sono racchiuse nella macro F(). // // Sull'ATmega328P la SRAM disponibile e' limitata. // F() permette di mantenere queste stringhe nella memoria Flash // invece di copiarle inutilmente nella SRAM. Serial.println(); Serial.println(F("========================================")); Serial.println(F(" BME280 - AVVIO")); Serial.println(F("========================================")); Serial.println(); // ------------------------------------------------------------- // BUS I2C // ------------------------------------------------------------- // Inizializza l'interfaccia I2C hardware. // // Su ATmega328P / Arduino UNO R3: // SDA = A4 // SCL = A5 Wire.begin(); Serial.println(F("Bus I2C inizializzato.")); Serial.println(F("SDA: A4")); Serial.println(F("SCL: A5")); Serial.println(); // ------------------------------------------------------------- // PRIMA RICERCA DEL BME280 // ------------------------------------------------------------- // Prova immediatamente a trovare e inizializzare il sensore. // // Non vengono impostate configurazioni avanzate del BME280: // lo sketch utilizza l'inizializzazione standard della libreria. sensoreDisponibile = inizializzaBME280(); // Memorizza il momento del tentativo. ultimaRicercaMs = millis(); // Se il sensore e' disponibile esegue subito la prima lettura. // // In questo modo all'avvio possiamo verificare immediatamente // che il BME280 stia fornendo dati, senza aspettare 60 secondi. if (sensoreDisponibile) { unsigned long adessoMs = millis(); leggiEStampaBME280(adessoMs); ultimaLetturaMs = adessoMs; } } // =============================================================== // LOOP // =============================================================== void loop() { // Legge una sola volta millis() all'inizio del ciclo. // // millis() restituisce il numero di millisecondi trascorsi // dall'avvio del microcontrollore. // // Usandolo possiamo gestire le temporizzazioni senza bloccare // il programma con delay(). unsigned long adessoMs = millis(); // ------------------------------------------------------------- // SENSORE NON DISPONIBILE // ------------------------------------------------------------- if (!sensoreDisponibile) { // Controlla se sono trascorsi almeno 5 secondi // dall'ultimo tentativo. if (adessoMs - ultimaRicercaMs >= INTERVALLO_RICERCA_MS) { // Aggiorna immediatamente il riferimento temporale. ultimaRicercaMs = adessoMs; Serial.println(F("[RICERCA] Nuovo tentativo BME280...")); // Riprova l'inizializzazione. sensoreDisponibile = inizializzaBME280(); // Se il sensore viene finalmente trovato, // esegue subito una prima misura. if (sensoreDisponibile) { leggiEStampaBME280(adessoMs); ultimaLetturaMs = adessoMs; } } // Se il sensore non e' disponibile non possiamo effettuare // alcuna misura. Terminiamo questo passaggio del loop(). return; } // ------------------------------------------------------------- // ACQUISIZIONE PERIODICA // ------------------------------------------------------------- // Controlla se sono trascorsi 60 secondi // dall'ultima acquisizione. if (adessoMs - ultimaLetturaMs >= INTERVALLO_LETTURA_MS) { // Salva il momento dell'acquisizione. ultimaLetturaMs = adessoMs; // Legge il sensore e stampa i risultati. leggiEStampaBME280(adessoMs); } } // =============================================================== // INIZIALIZZAZIONE BME280 // =============================================================== bool inizializzaBME280() { Serial.println(F("Ricerca BME280 sul bus I2C...")); // ------------------------------------------------------------- // TENTATIVO 0x76 // ------------------------------------------------------------- // Molti moduli BME280 utilizzano l'indirizzo 0x76. // // begin() prova a comunicare con il dispositivo presente // all'indirizzo indicato e inizializza la libreria. if (bme.begin(0x76)) { indirizzoBME280 = 0x76; } else { // ----------------------------------------------------------- // TENTATIVO 0x77 // ----------------------------------------------------------- // Se 0x76 non risponde, proviamo 0x77. if (bme.begin(0x77)) { indirizzoBME280 = 0x77; } else { // --------------------------------------------------------- // NESSUN BME280 TROVATO // --------------------------------------------------------- Serial.println(); Serial.println(F("ERRORE: BME280 non trovato.")); Serial.println(F("Controllare:")); Serial.println(F("- alimentazione del modulo;")); Serial.println(F("- collegamento GND;")); Serial.println(F("- SDA collegato ad A4;")); Serial.println(F("- SCL collegato ad A5;")); Serial.println(F("- compatibilita' elettrica del modulo.")); Serial.println(); Serial.println(F("Il programma riprovera' automaticamente.")); Serial.println(); return false; } } // ------------------------------------------------------------- // INFORMAZIONI DIAGNOSTICHE // ------------------------------------------------------------- // A questo punto begin() ha inizializzato correttamente // il BME280. Lo sketch non modifica manualmente le impostazioni // interne di campionamento o filtro del sensore. Serial.println(); Serial.println(F("BME280 trovato correttamente.")); Serial.print(F("Indirizzo I2C : 0x")); Serial.println(indirizzoBME280, HEX); // sensorID() legge l'identificativo del chip. // // Per un BME280 l'identificativo previsto e' normalmente 0x60. Serial.print(F("Chip ID : 0x")); Serial.println(bme.sensorID(), HEX); Serial.println(); Serial.println(F("Acquisizione avviata.")); Serial.println(F("Prima misura immediata.")); Serial.println(F("Misure successive ogni 60 secondi.")); Serial.println(); return true; } // =============================================================== // LETTURA E VISUALIZZAZIONE // =============================================================== void leggiEStampaBME280(unsigned long tempoMs) { // ------------------------------------------------------------- // LETTURA DELLE TRE GRANDEZZE // ------------------------------------------------------------- // readTemperature() restituisce la temperatura in gradi Celsius. float temperaturaC = bme.readTemperature(); // readHumidity() restituisce l'umidita' relativa in percentuale. float umiditaRH = bme.readHumidity(); // readPressure() restituisce la pressione in pascal [Pa]. float pressionePa = bme.readPressure(); // In meteorologia la pressione viene normalmente espressa // in ettopascal [hPa]. // // 1 hPa = 100 Pa // // Per questo dividiamo il valore restituito dalla libreria // per 100. float pressioneHpa = pressionePa / 100.0F; // ------------------------------------------------------------- // CONTATORE DELLE MISURE // ------------------------------------------------------------- numeroMisura++; // ------------------------------------------------------------- // CONTROLLO DEI VALORI // ------------------------------------------------------------- // Verifichiamo innanzitutto che la libreria non abbia restituito // NaN ("Not a Number"). // // NaN indica che il dato numerico non e' valido. bool datoNumericoValido = !isnan(temperaturaC) && !isnan(umiditaRH) && !isnan(pressioneHpa); // Controlliamo inoltre che i valori siano all'interno // dell'intervallo operativo del BME280. // // Questi controlli non certificano che la misura sia accurata: // servono soltanto a intercettare valori palesemente impossibili // o una lettura evidentemente corrotta. bool valoriNelRange = temperaturaC >= -40.0F && temperaturaC <= 85.0F && umiditaRH >= 0.0F && umiditaRH <= 100.0F && pressioneHpa >= 300.0F && pressioneHpa <= 1100.0F; // ------------------------------------------------------------- // INTESTAZIONE DEL BLOCCO // ------------------------------------------------------------- Serial.println(F("----------------------------------------")); Serial.print(F("[Misura ")); Serial.print(numeroMisura); Serial.print(F(" | uptime ")); Serial.print(tempoMs / 1000UL); Serial.println(F(" s]")); // ------------------------------------------------------------- // EVENTUALE ERRORE // ------------------------------------------------------------- if (!datoNumericoValido || !valoriNelRange) { Serial.println(F("ERRORE: lettura BME280 non valida.")); // Mostriamo comunque i valori ricevuti. // // Questo e' utile durante la diagnostica perche' permette // di capire che cosa ha effettivamente restituito il sensore // invece di nascondere semplicemente il problema. Serial.print(F("Temperatura : ")); Serial.print(temperaturaC, 2); Serial.println(F(" °C")); Serial.print(F("Umidita' : ")); Serial.print(umiditaRH, 2); Serial.println(F(" %RH")); Serial.print(F("Pressione : ")); Serial.print(pressioneHpa, 2); Serial.println(F(" hPa")); Serial.println(F("----------------------------------------")); Serial.println(); return; } // ------------------------------------------------------------- // VISUALIZZAZIONE NORMALE // ------------------------------------------------------------- // Le etichette mantengono sempre lo stesso ordine. // // Questo rende il Serial Monitor immediatamente leggibile // anche lasciando il programma in funzione per molto tempo. Serial.print(F("Temperatura : ")); Serial.print(temperaturaC, 2); Serial.println(F(" °C")); Serial.print(F("Umidita' : ")); Serial.print(umiditaRH, 2); Serial.println(F(" %RH")); Serial.print(F("Pressione : ")); Serial.print(pressioneHpa, 2); Serial.println(F(" hPa")); // IMPORTANTE: // // Questa e' la pressione assoluta misurata dal BME280 // nel punto in cui si trova il sensore. // // Non e' automaticamente la pressione normalizzata // al livello del mare. Serial.println(F("----------------------------------------")); Serial.println(); }