RTC DS3231: Scrittura Data-Ora in "Stringhe" e lettura temperatura

Buongiorno a tutti!

L'argomento su cui mi sono incaponito in questi giorni è l'impiego di un RTC all'interno del mio primo vero progettino (lettura di sensori multipli e scrittura dei risultati su SD).

Per quanto riguarda l'RTC, anzichè utilizzare le svariate librerie già pronte, ai fini didattici, ho preferito studiare la libreria "wire" e provare ad estrarre i valori direttamente dai registri cercando di "interpretare" il datasheet. E qui arriva il primo problema.

Per quanto riguarda la data e l'orario sono riuscito senza particolari difficoltà ad ottenere il risultato sperato. Relativamente alla temperatura, dal datasheet ho visto che viene fornita sotto forma di due componenti, ossia la parte intera MSB e la parte frazionaria LSB. Cercando info al riguardo ho trovato la formulazione che ho inserito nel codice per determinare il valore in Celsius, ma non riesco a comprenderne pienamente il significato, oltre a non sapere se tale formulazione risulta corretta. Qualche dritta al riguardo? (suggerimenti, articoli, ecc... )

Il secondo problema che sto incontrando verte invece sul trattamento dei dati ottenuti dall' RTC. Sulla SD card, desidero accompagnare il valore letto dai sensori con la data e l'ora di lettura, nello specifico tale indicazione, per come sto strutturando la restante parte del codice dovrebbe essere nel formato "aaaammgg_hhmmss" (anno mese giorno _ ora minuti secondi). La prova che ho fatto ora nel codice allegato consiste nel concatenare una stringa con i vari "pezzi" per ottenere quello che mi serve. Dato che per non avere problemi di "ricerca" dei record all'interno dei files salvati sulla SD desidero che ogni riga del file stesso non cambi la propria lunghezza, è necessario modificare la stringa sopra descritta inserendo degli "zeri" nel momento in cui le singole componenti (mese, giorno, ora, ecc...) diano valori a cifra singola (l'RTC per esempio per il mese di febbraio fornisce il valore "2" anzichè "02" come mi servirebbe. Ho fatto "n" tentativi utilizzando semplici cicli "if" ma mi sto incasinando con i tipi di variabile in gioco e non riesco a venirne a capo. Qualche dritta? Durante le ricerche di una possibile soluzione a tale problemino ho letto che le stringhe sarebbe opportuno non utilizzarle perchè occupano un sacco si spazio; esisiste una migliore soluzione a quanto ho per il momento elaborato?

Ringrazio per la cortese attenzione e per gli eventuali suggerimenti.
Ciao, Giuliano.

PS: Il codice in oggetto è il seguente:

// Collegamenti RTC DS3231 Arduino UNO
// GND RTC --> GND Arduino UNO
// Vcc RTC --> 5V Arduino UNO
// SDA RTC --> A4 Arduino UNO
// SLC RTC --> A5 Arduino UNO

// Collegamenti Arduino MEGA

// Includo la libreria per comunicazione I2C
#include <Wire.h>         //http://arduino.cc/en/Reference/Wire (incluso con Arduino IDE)

// Dichiarazione costanti
const unsigned int baud_rate_seriale=9600;  // velocità di comunicazione porta seriale

// Dichiarazione variabili RTC DS3231
byte errore_RTC;          // Errore generico di comunicazione con modulo RTC
float temperatura_RTC;    // Temperatura misurata dal modulo RTC

void setup(){
  
  // Inizializzazione porta seriale
  Serial.begin(baud_rate_seriale);
  
  // Inizializzazione libreria Wire
  Wire.begin();

  delay(1000);
  
}

void loop(){
  
  // Inizializzazione trasmissione RTC DS3231 (0x68) per lettura data e ora
  Wire.beginTransmission(0x68);               // Indirizzo I2C RTC
  Wire.write((byte)0x00);                     // Inizio registro data-ora
  errore_RTC = Wire.endTransmission();
 
  if (errore_RTC != 0){
        Serial.println(errore_RTC);
  }
  
  // Lettura data e ora
  // Recupero i 7 byte relativi ai corrispondenti registri (vedi datasheet)
  Wire.requestFrom(0x68,7);
  byte secondi=Wire.read();
  byte minuti=Wire.read();
  byte ore=Wire.read();
  byte giorno_sett=Wire.read();
  byte giorno_mese=Wire.read();
  byte mese=Wire.read();
  byte anno=Wire.read();

  // Inizializzazione trasmissione RTC DS3231 (0x68) per lettura temperatura
  Wire.beginTransmission(0x68);            // Indirizzo I2C RTC
  Wire.write((byte)0x11);                  // Inizio registro temperatura
  Wire.endTransmission();

  // Lettura temperatura
  // Recupero i 2 byte relativi ai corrispondenti registri (vedi datasheet)
  Wire.requestFrom(0x68,2);
  int MSBtemp=Wire.read();    // Parte intera
  int LSBtemp=Wire.read();    // Parte frazionaria
  
  // Trasformazione in gradi Celsius dei dati letti dal sensore
  // sistemare questa formula ---------------------------------
  temperatura_RTC = MSBtemp + (LSBtemp >> 6)*0.25;  // E' corretto ???
  
  // Temperatura misurata internamente all'RTC DS3231
  Serial.print(F("Temperatura "));
  Serial.print(temperatura_RTC);
  Serial.print(F(" *C "));

  // Data RTC DS3231
  Serial.print(F("Data "));
  Serial.print(anno,HEX);
  Serial.print(F("-"));
  Serial.print(mese,HEX);
  Serial.print(F("-"));
  Serial.print(giorno_mese,HEX);
  // Serial.print(giorno_sett,HEX);

  // Orario RTC DS3231
  Serial.print(F(" Ora "));
  Serial.print(ore,HEX);
  Serial.print(F(":"));
  Serial.print(minuti,HEX);
  Serial.print(F(":"));
  Serial.println(secondi,HEX);

  // Stringa per DataOra per successiva scrittura su MicroSD Card
  String DataOra = ("20" + String(anno,HEX) + String(mese,HEX) + String (giorno_mese,HEX) + "_" + String(ore,HEX) + String(minuti,HEX) + String(secondi,HEX));   // ???????????????????????
  Serial.println(DataOra);
  
  delay(5000);
  
}

Evita la classe String, usa allocazione dinamica e può portare a frammentazione della memoria SRAM.
Se puoi usare delle print successive meglio spezzare con più print() come hai fatto prima di DataOra.

Altrimenti la cosa migliore è usare le stringhe classiche del C (array/vettori di char terminati da null ovvero 0)
Te la crei da te oppure puoi usare
A) la snprintf() che è simile alla printf() del classico C, "stampi" dentro a un vettore/buffer/stringa

char buf[10];
...
snprintf(10,buf,"Mese: %02d",mese);       // se mese 1 => Mese: 01    02=2 char fillati con 0
Serial.println(buf);

B) usa li libreria PString che sostituisce la snprintf() ed è più leggera e simile alla Serial.print()

char buf[10];
PString mystring(buf, sizeof(buf));
mystring.print("Mese: ");
if(mese<10) mystring.print('0');
mystring.print(mese);
Serial.println(buf);

Non credo la PString fa il discorso di fillare con gli 0, se non lo fa, devi usare una if, se mese<10 aggiungi la stampa di uno 0 per prima cosa.
Esiste per la PString un comando format() tipo snprintf() ma internamente richiama questa funzione, quindi se usi format() hai la stessa cosa di snprintf() ovvero un codice eseguibile un pò più grosso (la sprintf è pesante/cicciona)

Ciao, Grazie per la risposta.

Relativamente al metodo A) avevo già provato, ma se provo con i giorni o le ore per esempio mi da dei risultati errati. Penso che sia legato al formato dei dati (HEX ?) ma non ne vengo a capo. Se provo il codice di seguito riportato mi risponde "21" anzichè "15"

char buf[10];
snprintf(buf,10,"ore:%02d", ore);
Serial.println(buf);

Se provo con il codice seguente mi risulta "16" anzichè "15"

char buf[10];
snprintf(buf,10,"ore:%02d", (ore,HEX));
Serial.println(buf);

Sono decisamente inesperto in merito alla manipolazione delle variabili e non riesco a capire come risolvere.

In merito al metodo B) ora mi studio il tuo codice e provo.

Grazie ancora
Ciao, Giuliano

L'hex non c'entra niente, il problema potrebbe essere che %d vuole un int, e il tuo ore è un byte (quindi un unsigned char), fai quindi un cast ad int, potrebbe funzionare...

... ma...

... Usa il metodo B per i problemi già citati, è la cosa migliore.

Come dice @sukko, il cast a int è quella int scritta tra tonde:
snprintf(buf,10,"ore:%02d", (int)ore);

Per quanto riguarda HEX, non capisco perchè lo usi, serve per stampare i numeri in esadecimale invece che in decimale, 20 decimale = 14 in esadecimale
Anche nel tuo codice stampi date e mesi etc. con Hex, ma che senso ha ?
Serial.print(anno,HEX); ?? 2018 stampa 7E2

Ovvero tutti i numeri SONO NUMERI, li puoi stampare in decimale, esadecimale o binario ma sempre lo stesso numero sono. 20 in decimale lo puoi scrivere in esadecimale come 14, oppure binario 10100 ma per la macchina sempre quel valore è.

Buonasera!

Ho provato a usare "int" come suggerito ma non cambia nulla.

Se non metto gli "HEX" che ho indicato nel codice non ottengo i valori corretti. Dipende forse da come restituisce i valori l'RTC? Non so cosa dire....

Per quanto riguarda "PString" cerco la libreria on-line perchè non la conosco proprio.

Grazie a tutti.
Buonasera, Giuliano.

Per completarela la risposta @nid69ita
riporto i risultati del codice originale, e dello stesso codice eliminando gli "HEX"

Stampa dell'output del codice originale (quello postato nel primo intervento)

Temperatura 20.00 *C Data 18-2-20 Ora 20:8:35
2018220_20835
Temperatura 20.00 *C Data 18-2-20 Ora 20:8:40
2018220_20840
Temperatura 20.25 *C Data 18-2-20 Ora 20:8:45
2018220_20845
Temperatura 20.25 *C Data 18-2-20 Ora 20:8:50
2018220_20850
Temperatura 20.25 *C Data 18-2-20 Ora 20:8:55
2018220_20855
Temperatura 20.25 *C Data 18-2-20 Ora 20:9:0
2018220_2090
Temperatura 20.25 *C Data 18-2-20 Ora 20:9:5
2018220_2095
Temperatura 20.25 *C Data 18-2-20 Ora 20:9:10
2018220_20910

Stampa dell'output del codice avendo eliminato i vari "HEX"
Il codice è stato lanciato un paio di minuti dopo il precedente

Temperatura 20.50 *C Data 24-2-32 Ora 32:17:4
2024232_32174
Temperatura 20.75 *C Data 24-2-32 Ora 32:17:9
2024232_32179
Temperatura 20.50 *C Data 24-2-32 Ora 32:17:20
2024232_321720
Temperatura 20.75 *C Data 24-2-32 Ora 32:17:25
2024232_321725
Temperatura 20.50 *C Data 24-2-32 Ora 32:17:36
2024232_321736
Temperatura 20.75 *C Data 24-2-32 Ora 32:17:41
2024232_321741
Temperatura 20.75 *C Data 24-2-32 Ora 32:17:52
2024232_321752

Ho risolto utilizzando la libreria PString come suggerito da "nid69ita".
Ho dovuto tuttavia inserite gli "HEX" per ottenere i valori corretti. Bho.....

Riporto lo stralcio del codice:

  // Stringa per DataOra per successiva scrittura su MicroSD Card
  char DataOra[20];
  PString mystring(DataOra,sizeof(DataOra));
  //mystring.print("Data: ");
  mystring.print("20");
  mystring.print(anno,HEX);
  if (mese<10) mystring.print('0');
  mystring.print(mese,HEX);
  if (giorno_mese<10) mystring.print('0');
  mystring.print(giorno_mese,HEX);
  mystring.print("_");
  if (ore<10) mystring.print('0');
  mystring.print(ore,HEX);
  if (minuti<10) mystring.print('0');
  mystring.print(minuti,HEX);
  if (secondi<10) mystring.print('0');
  mystring.print(secondi,HEX);
  Serial.println(DataOra);

Ciao a tutti, Giuliano

Probabilmente i dati sono spediti con qualche codifica, forse BCD o packed BCD

Buongiorno.

Qualcuno per caso ha qualche dritta circa il problema legato alla temperatura? (vedasi post iniziale).

Grazie mille.
Ciao, Giuliano

Questo ? temperatura_RTC = MSBtemp + (LSBtemp >> 6)*0.25;
Dal datasheet del chip: https://datasheets.maximintegrated.com/en/ds/DS3231.pdf
"Temperature is represented as a 10-bit code with a resolution of 0.25°C and is accessible at location 11h and 12h. The temperature is encoded in two’s complement format. The upper 8 bits, the integer portion, are at location 11h and the lower 2 bits, the fractional portion, are in the upper nibble at location 12h. For example, 0001100101b = +25.25°C."
Le librerie per questo RTC fanno il calcolo però tenendo conto del complemento a 2:

temp_msb = Wire.read();
temp_lsb = Wire.read() >> 6;
if ((temp_msb & 0x80) != 0)  
    nint = temp_msb | ~((1 << 8) - 1);      // if negative get two's complement
else
    nint = temp_msb;
rv = 0.25 * temp_lsb + nint;

E dalla libreria non capisco perchè moltiplicano per 0.25 solo una parte
(come invece ti aspetterei rv = (0.25 * temp_lsb + nint) ; )
Penso che solo la parte decimale sia in passi da 0.25 (almeno credo)

Si, si...proprio quello.
Avevo letto anch'io il datasheet ma non capisco bene come funziona.

I primi 8 bit mi danno la parte intera della temperatura, infatti nell'esempio 00011001 corrisponde a 25°.
Non riesco a capire come funzioni il codice per la lettura della parte frazionaria (01b nell'esempio) e come operi l'operatore di shift (<< e >>). Cosa succede nello specifico alle righe 2 e 4 del codice che hai riportato?

Grazie mille
Giuliano