Progetto preliminare per un logger subacqueo

Apro questo topic come idea preliminare, e giusto nel caso poi il tutto (se funzionera' :grin:) possa venire utile anche a qualcun'altro.

Premetto che al momento e' in fase solo preliminare, mi mancano alcuni componenti che devono arrivare, quindi il software lo sto scrivendo "al volo" per portarmi avanti, dato che posso testarne solo una parte (inoltre, conoscendomi, ci saranno molto probabilmente alcune modifiche che mi verranno in mente in corso d'opera da aggiungerci)

Mi e' stato chiesto da una onlus se esistesse un "coso" che potesse essere agganciato ad un ROV, per mandarlo sott'acqua e fare una serie di registrazioni di profondita' e temperatura dell'acqua, ma che avesse anche uno schermo che fosse visibile dalla camera di navigazione durante l'esplorazione (e che possibilmente non costasse un rene) ... dato che l'unica cosa che fa log di temperatura e profondita' che ho trovato per sub, apparentemente, oltre a costare uno sproposito e' un "cosino" senza neppure un display, e che per giunta legge solo ogni 10 secondi, e' nata l'idea di autocostruirlo.

L'idea di partenza e' stata di farlo non solo "foolproof", ma proprio "blindato", dato che non e' detto che un'operatore di ROV se ne intenda anche di apparecchiature elettroniche, e che potrebbe finire nelle mani di chiunque (ho visto cose ...), cosi ho optato per un sistema con un solo comando, l'interruttore di accensione, che infilero' nel corpo in alluminio di un vecchio faro per camere subacquee che mi hanno procurato, che ha un'anello esterno con un magnete come interruttore (piu "blindato" di cosi ... :grin:).

Il circuito e' piuttosto elementare a livello di hardware (la partita a tetris che ci vorra' poi per far stare tutto nel contenitore e' un'altro discorso), un 328P (poi usero' un PB, ma solo perche' ne ho alcuni inutilizzati, va bene anche il classico 328P), un display OLED da 1.3, una microSD con il suo zoccolino, un sensore che legga sia temperatura che pressione e sia adatto a lavorare sott'acqua, una volta opportunamente resinato, e poco altro, ho shakerato bene e ne e' uscito questo schema:

Il sensore (MS5837-30BA) condivide l'I2C con il display, mentre la SD ha la sua SPI, che condivide il connettore con l'ICSP portandoci anche il reset.

L'idea e' di usare la MCU senza quarzo, con l'oscillatore interno, far andare tutto a 3.3V (circa, usero' una cella LiPo), e di leggere i dati una volta ogni secondo e mezzo circa (4 letture ogni 50mS circa di cui fare la media), visualizzarli sul display, scriverli sulla microSD in formato CSV, fare un breve lampeggio del led per confermare la scrittura ed attendere fino alla successiva lettura, questo come comportamento generale ... all'accensione dovro' fare un controllo iniziale per vedere se tutto va bene, se il display inizializza, se c'e' la microSD inserita, se la batteria e' troppo scarica, in caso di un qualsiasi errore mandero' tutto in blocco in una funzione che fara' lampeggiare in continuazione il led (250mS on/250mS off) con un while(1), mentre se tutto sara' ok, iniziera' direttamente a fare le letture e visualizzare e scrivere i dati senza altri interventi.

Sto' buttando giu (dalla finestra) qualche riga di software, appena inizia ad avere un senso lo posto qui per eventuali suggerimenti e critiche.

1 Like

Una domanda per chi li ha gia usati, a proposito dei display oled con l'SSD1306, ho notato che praticamente in tutti gli esempi includono due librerie, la ssd1306 e la gfx, ma a quello che ho capito la gfx serve solo per la grafica, volendo usare solo il testo, si puo evitare di includerla ? (la cosa risparmia spazio ?)

I pin non usati li lasci liberi?
Ma si tanto sotto l'acqua non c'è inquinamento elettromagnetico.
Quindi dovrebbe bastare ed avanzare un pull-up.
Poi il PCB è pure ben schermato, diversamente ci piazzi un 100n che poi decidi se saldarli o meno.

Cosa intendi per pin non usati ? ... il tutto e' in un contenitore metallico impermeabile, non a contatto con l'acqua.

Gli unici pin che usero' sono SPI, I2C, un'uscita per il led e l'ingresso ADC7 collegato alla batteria con un partitore (dovro' impostare AREF con gli 1.1V interni), e basta ... mi sembra che i pin non definiti possano restare senza connessioni, credo (o sbaglio ?).

Solitamente i pin non usati sono impostati con pull-up interna.
Per ulteriore immunità elettromagnetica si terminano con un condensatore verso GND. Ma come dicevo, sotto l'acqua non
c'è inquinamento elettromagnetico e al massimo c'è quello generato dal
ROV stesso che presumo abbia delle turbine brushless.
Tuttavia hai detto che il pcb sarà inserito dentro un contenitore di alluminio e quindi ben schermato.
Tuttavia ancora se ci fossero dei malfunzionamenti che tu imputi ad inquinamento elettromagnetico ti potresti togliere il dubbio saldando
i condensatori che hai previsto, diversamente dovresti rifare il PCB per toglierti il dubbio.

Ciao.

Ah, si, ho capito cosa intendevi adesso ... no, sara' tutto all'interno di un robusto cilindro in alluminio, peraltro sott'acqua, e si avevo pensato di dichiarare tutti i pin inutilizzati come INPUT_PULLUP ... il ROV non dovrebbe generare piu di tanti disturbi, in effetti.

Lo stampato dovra' essere per forza piccolo, un cerchio di 40mm con una "fetta" rimossa dove dovro' posizionare in verticale la schedina con lo zoccolo della microSD e quella con la presa microusb per ricaricare la batteria, ma penso di poterci far stare lo stesso le predisposizioni per dei condensatori 0603, male che vada occupo anche il lato opposto se non ci stanno tutti sopra.

E' stato piu semplice di quello che credevo, farceli stare tutti ...


Purtroppo non ci sta altro che l'1.3 come oled, nulla di piu grande, dovro' trovare un sistema per riuscire a visualizzare nel modo piu chiaro possibile almeno 3 righe di caratteri riempiendolo al massimo ...

Oppure, se ti serve una striscia scorrevole di lunghezza fissa ma dinamica
chiedi che ciò codice.
Dinamica intendo che i campi si aggiornano mentre la striscia scorre.
Il codice però attualmente non usa questo display, ma può usarlo modificandolo. Occhio che se tu sei abituato con delay il mio codice non striscerà. :smiley:

Ciao.

No, mi serve proprio che visualizzi almeno tre righe una sopra l'altra, che si aggiornano solo al momento della successiva lettura, purtroppo.

Ebbene si, usero' i delay (l'orrore ... l'orrore !!! :rofl:), ma c'e' un motivo preciso, il sistema non deve ricevere assolutamente nessun comando esterno e deve fare solo quelle operazioni, ciclicamente, con intervalli di circa 1500ms, mi sembrava inutile impostare tutto con millis ... gia adesso mi dice che uso troppa memoria (text section exceeds available space in board), e non so come ridurlo (e neppure e' completo :crazy_face:)

... se ti serve SOLO testo, usa la molto più semplice e leggera SSD1306Ascii ... la installi dal "Library Manager" è ben documentata e funziona molto bene :wink:

Guglielmo

... ricordati di mettere tutte le stringhe fisse in PROGMEM.

Guglielmo

... se vuoi stare tranquilllo e hai spazio, li dichiari come INPUT e li mandi a massa con una resistenza da un 220Ω ... e non te ne preoccupi più :grin:

Guglielmo

Ieri sera ho buttato giu questa robaccia, che ancora devo provare in pratica (non mi sono ancora arrivati alcuni componenti), piu che altro per vedere se compilava ... cosi com'e' arriva alla fine della compilazione senza errori, ma poi mi dice che sto usando il 101% della memoria disponibile (ed il 65% della ram)

[code]
#include <Wire.h>
#include <MS5837.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define ledp 0
float profon;
float temper;
char strlett[5];
char strtemp[5];
char strprof[5];
char vstrlett[5];
char vstrtemp[5];
char vstrprof[5];
char strDati[35];
unsigned int lettura = 0;
MS5837 sensor;
File fileDati;

void errore() {
  while (1) {
    digitalWrite(ledp, HIGH);
    delay(250);
    digitalWrite(ledp, LOW);
    delay(250);
  }
}

void leggisens() {
  for (byte x = 0; x < 4; x++) {
    sensor.read();
    temper += sensor.temperature();
    profon += sensor.depth();
    delay(40);
  }
  temper /= 4;
  profon /= 4;
  itoa(lettura, strlett, 10);
  dtostrf(temper, 4, 1, strtemp);
  dtostrf(profon, 4, 1, strprof);
  sprintf(strDati, "L %s, T %s C, P %s m", strlett, strtemp, strprof);
}

void dispdati() { // scrive i dati sul display cancellando prima i precedenti
  display.setTextColor(BLACK);// cancella vecchi
  display.setCursor(0, 0);
  display.print(F("L "));
  display.print(vstrlett);
  display.print(F("T "));
  display.print(vstrtemp);
  display.println(F(" C"));
  display.print(F("P "));
  display.print(vstrprof);
  display.print(F(" m"));
  display.setTextColor(WHITE); // scrive nuovi
  display.setCursor(0, 0);
  display.print(F("L "));
  display.print(strlett);
  display.print(F("T "));
  display.print(strtemp);
  display.println(F(" C"));
  display.print(F("P "));
  display.print(strprof);
  display.print(F(" m"));
  strcpy(vstrprof, strprof); // memorizza per ciclo successivo
  strcpy(vstrlett, strlett);
  strcpy(vstrtemp, strtemp);
}

void scrivisd() {
  fileDati = SD.open("data.csv", FILE_WRITE);
  if (fileDati) {
    fileDati.println(strDati);
    fileDati.close();
  digitalWrite(ledp, HIGH);
    delay(50);
    digitalWrite(ledp, LOW);
  }
  else {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println(F("SD ERR"));
    display.display();
    errore();
  }
}

void setup() {
  pinMode(ledp, OUTPUT);
  digitalWrite(ledp, LOW);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) errore(); //display ko, blocca
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println(F("ATTENDI"));
  display.display();
  if (!SD.begin(10)) { //sd ko, blocca
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println(F("NO SD"));
    display.display();
    errore();
  }
  if (!sensor.init()) { //sensore ko, blocca
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println(F("SENS ERR"));
    display.display();
    errore();
  }
  display.clearDisplay(); //tutto ok
  display.setCursor(0, 0);
  display.println(F("OK"));
  display.display();
  delay(1000);
}

void loop() {
  leggisens();
  dispdati();
  scrivisd();
  delay(1500);
}
[/code]

Pero' poi mi sono venuti un paio di dubbi ... intanto avevo selezionato una Nano come scheda (perche' non ho trovato alcun "bare 328" o simili voci), non e' che cosi mi considera la presenza di un bootloader e mi toglie parte della memoria disponibile ? ... come dico all'IDE che voglio usare un 328PB senza bootloader e senza quarzo esterno ? (ma questo per dopo, quando mi arrivera' l'adattatore da TQFP a DIL per testarlo in condizioni il piu reali possibile)

Secondo, dov'e' che ho fatto cosi tanto casino da esaurire la memoria con cosi poche righe di codice ?

Considerate che forse andra' modificato ancora, li ho sentiti e vorrebbero poterlo usare anche senza SD inserita, ma quello dopo, ora vorrei vedere se riesco in qualche modo a ridurre l'occupazione di memoria ... sospetto che la libreria del display di Adafruit carichi un sacco di roba, quindi oggi quando rientro (ora devo scappare, corvee spese per mamma) provero' a sostituirla con quella che mi avete consigliato per l'ascii e rifaro' le prove..

Le librerie per i display di Adafruit sono dei veri MATTONI ... se puoi, evitale come la peste ...

Poi c'è la SD che mangia anche lei un sacco di memoria per i buffer di I/O ... ma li c'è poco da fare.

Guglielmo

Anche la scrivisd() è sbagliata ...

... l'apertura ed il controllo della SD lo fai una sola volta a livello di setup(), poi, nel loop(), devi solo scrivere il tuo record e chiamare la flush() per assicurarti che avvenga la scrittura fisica sulla SD (così, se manca alimentazione, i dati acquisiti sino a quel momento sono scritti e non stanno nel buffer).

Guglielmo

... poi spiegami perché fai tutti questi passaggi ... la sprintf() fa lei le conversioni, basta che specifichi il tipo di dato e come lo vuoi formattato, non serve che le fai tu prima e gli passi delle stringhe!

Non solo, puoi specificare se vuoi lunghezze fisse, zeri davanti, numero di decimali, ecc. ecc. ... richiede un po' di studio perché ha tantissime possibilità, ma poi fai fare tutto a lei :wink:

Guglielmo

Interessante.
Ma usando solo file di testo, quindi eliminando il display ed usando una fram o simili sarebbe "meglio"?

Non funge con il 328 la sola conversione da float a stringa, per cui è costretto ad usare dtostrf(). Però lettura lo può formattare con %d senza passare per itoa.

Ciao.

... ti do un paio di esempi da cui partire ... data/ora presa da un DS3231:

// Display Time
   sprintf(tString, "%02u : %02u : %02u  -  %02u . %02u . %04u", tmDS3231.tm_hour, tmDS3231.tm_min, tmDS3231.tm_sec, tmDS3231.tm_mday, tmDS3231.tm_mon, (tmDS3231.tm_year + 1900));

... valori presi da un contatore geiger:

// Display radiation
   sprintf(tString, "%03u CPS  -  %03u CPM  -  %6.2f uS/h", maxCPS, maxCPM, maxuSh);

... valori presi da un BME280:

// Display Temp. Press, Hum
   sprintf(tString, "%3.1f deg.   %5.1f mmHg   %3.1f %% RH", T, P, H);

... come vedi tutte le conversioni, arrotondamnenti, formattazioni, sono fatte tutte nella sprintf() :wink:

Guglielmo