Ottimizzare memoria dinamica

Buonasera ragazzi, avrei bisogno di un altro aiuto.

Nel progetto che sto portando avanti mi trovo con una Mega originale e un display Nextion con il quale mi interfaccio per gestire un acquario (SD, RTC, rabbocco automatico, temperatura, Ph e TDS).

Funziona tutto senza problemi per adesso, però ho raggiunto il 76% della memoria dinamica e giustamente mi è spuntato l'avviso di eventuali problemi di stabilità.

La parte che mi porta via più memoria di tutta è quella dove invio i comandi al Nextion, per esempio:

myNextion.setComponentText("btnStartCalPh", String("Calib"));
        myNextion.sendCommand("btnStartCalPh.bco=1024"); 
        myNextion.sendCommand("ref btnStartCalPh");

credo per il fatto di fare un uso intenso delle stringhe, può essere?

C'è un modo per ottimizzare questo discorso secondo voi?

La libreria che utilizzo è la Nextion (GitHub - bborncr/nextion: A simple Nextion HMI library for Arduino)

Grazie per la disponibilità.

Sicuramente si, l'uso della classe Strign su Arduino è il male, ti porterà a blocchi e malfunzionamenti.
Inizia con il rimuovere tutte le String in favore degli array di char, fatto questo tutte le stringhe fisse in flash con la funzione F (Es. F("Ciao") )
Altra cosa che puoi fare per migliorare la situazione è individuare tutte le stringhe ripetute, definirne una e riusarla. Dal tuo stralcio di codice si nota che la sottostringa "btnStartCalPh" è presente tre volte il tuo pezzo di codice potrebbe diventare:

char parteRipetuta[14] = "btnStartCalPh"; //globale
char daInviare[24];
myNextion.setComponentText(parteRipetuta, F("Calib"));
strncpy(daInviare, parteRipetuta, strlen(parteRipetuta));
strncat(daInviare, F(".bco=1024"), 9);
strncat(daInviare, '\0', 1);
myNextion.sendCommand(daInviare);
strncpy(daInviare, F("ref "), 3);
strncat(daInviare, parteRipetuta, strlen(parteRipetuta));
strncat(daInviare, '\0', 1);
myNextion.sendCommand(daInviare);

Scritto senza ovviamente provarlo va verificato.
Altra cosa anche altre stringhe che potebbro essere riciclare (Es. .bco=) definiscile una sola volta e ricicla il più possibile.
Se poi metti tutto il programma magari è possibile otimizzare anche altre cose

Buongiorno fabpolli, questa mattina dopo qualche ricerca un po’ mirata ho trovato un’interessante discussione di 4 anni fa dove era emerso questo problema utilizzando questa libreria.

In quel topic una buona anima si era preso la briga di modificarla in modo da poter implementare la funzione F( nell’invio delle stringhe.

File H.

void sendCommand(const char* cmd);
  void sendCommand( const __FlashStringHelper * command ); // <-- your new method (i.e., routine)

File .CPP

void Nextion::sendCommand(const char* cmd){  // <-- the current method
  while (nextion->available()){
 nextion->read();
  }//end while
  nextion->print(cmd);
  nextion->write(0xFF);
  nextion->write(0xFF);
  nextion->write(0xFF);
}//end sendCommand

void Nextion::sendCommand(const __FlashStringHelper * cmd){ // <-- Your new method
  while (nextion->available()){
 nextion->read();
  }//end while
  nextion->print(cmd);
  nextion->write(0xFF);
  nextion->write(0xFF);
  nextion->write(0xFF);
}//end sendCommand

Metto il link se servisse a qualcun altro https://forum.arduino.cc/index.php?topic=427978.15

Sempre prendendo spunto da quella discussione, ho modificato i messaggi di comparazione con quelli in arrivo dalla seriale da

const char myMessage_0[] = "65 0 3 0 ffff ffff ffff";

a

const char myMessage_0[] PROGMEM = "65 0 3 0 ffff ffff ffff";

e di conseguenza la comparazione da

if (strcmp(message, myMessage_0) == 0) {

a

if (strcmp_P(message, myMessage_0) == 0) {

In questo modo sono passato dal 76% di utilizzo della memoria dinamica al 58%.

Ad ogni modo ti ringrazio per il tuo suggerimento, come dici te molto spesso la stessa stringa è ripetuta più volte e sarebbe opportuno definirla una volta sola e riutilizzarla.

Tieni presente che se esti con l'uso della classe String ci sono comunque ottime probabilità di sperimentare blocchi improvvisi, errori apparentemente casuali, ecc. proprio perché la classe String alloca e disalloca mmoria dinamica e così facendo si frammnta e arrivi a saturarla in fase di runtime, ricorda che su Arduino non hai un garbage collector che sistema le cose quindi l'intervento di eliminazione dell'uso della classe string lo considero comunque indispensabile.

Ah ok, quindi nonostante la memoria dinamica sia poco più della metà possono succedere blocchi casuali lo stesso oppure stai parlando di situazione come quella precedente dove ero al 76%?

Eventualmente cos'altro si potrebbe fare per migliorare ancora le cose? Nel senso, dovendo comunicare con un HMI dovrei evitare di usare la libreria in questione?

Ti ringrazio per l'aiuto!

Si anche se ne usi poca alla lunga puoi sperimentari blocchi e non dpnde dalla libreria per l'uso del dislay ma dall'uso delle variabili String, cerca sul forum e noterai circa centomila topic dove viene sconsigliato l'uso della classe String e il perché, devi sostituire tutte le variabili di tipo String con le stringhe classiche del C (Array di char) e le relative funzioni di manipolazione (nell'esempio della mia prima risposta ne hai un paio di esempi)
La libreria per il monitor non ha problemi, io personalmente non la uso perché alla fine è un invio/ricezione via seriale ma non è quella il problema, quella di occupa solo di inviare/ricevere e non soffre di problemi particolari.
Ddica il massimo sforzo nll'eliminazione delle String quello si che è importante

@Bubba, il problema della memoria dinamica (e quindi String) è che non esiste la "garbage collection"
ovvero non c'e' una "pulizia della memoria"
Se tu hai 100 byte liberi, poi allochi i primi 40 poi altri 50, occupi 90 in tutto su 100
se non usi più i primi 40, NON hai liberi 50 byte ma 40 e poi 10 NON contigui
se necessiti di una String da 45... fregato, non ne hai abbastanza

Praticamente si ha un progressivo cumulato che prima o dopo sarà troppo grande per la memoria disponibile..

Adesso sto giocando con il saldatore e millefori per preparare la schedina che mi serve per interfacciare il tutto.

Ma per assurdo, se io mi preparassi tutti gli array di char che so di utilizzare nel programma (senza concatenare ogni volta) sarebbe una buona cosa? Alla fine non sono tantissimi i comandi da inviare, per carità, un bel lavoro ma se è da fare si fa :slight_smile:

Se ti prepari i vari array con i comandi / stringhe riciclabili e poi crei degli array solo quando ti serve concatenare i comandi per inviarli è sicuramente una buona cosa.
Puoi anche crare un array di char globale avente come dimensione un numero di caratteri uguale a quello del comando più lungo che dovrai inviare (più una posizione per il terminatore di stringa) e a quel punto la memoria che usi è già conoscita in fase di compilazione (al di la dlle variabili locali, stack, ecc.) e sai sempre se sei entro i limiti, con ottime probabilità di non incappare in problematiche. Quando possibile è consigliabile avere variabili locali e non globali ma in questo caso puoi scegliere anche la via della globale e riusare sempre tale array per comporre ed inviare i comandi.
Io ho fatto così per i cronotermostati che ho in casa, dialogano da/verso il nextion / RS485 sfruttando un array dimensionato nel modo che ti ho descritto. Da più di un anno e mezzo fanno il loro senza blocchi o problemi, e da quando li ho messi sotto un gruppo di continuità sono 201 giorni di uptime senza mai problemi. Spero i non ssermela tirata :smiley:

Ma che tirartela, è sempre bello leggere qualcuno che ha fatto un progetto funzionante da tempo e non solo a banco!

Dai questa sera guardo fuori come impostare il tutto relativamente a come mi è stato consigliato e sicuramente torno a chiedere un vostro parere in modo impostare il tutto al meglio una volta per tutte.

Grazie mille intanto!

:smiley: :smiley: :smiley: Tirarmela nel senso di essermi portato sfiga e adesso vanno tutti in crash! :smiley:

Ahhhh ok :smiley: :smiley: ::slight_smile:

Sto guardando fuori, però vorrei capire bene prima di iniziare..

Porta pazienza se ti faccio domande stupide ma non sono del settore e proprio dire qualche cavolata bella e buona..

La prima domanda è: se qui metti la F(
myNextion.setComponentText(parteRipetuta, F("Calib"));

perchè non posso semplicemente metterla anche qui in questo modo e rendere il tutto più semplice?
myNextion.setComponentText(F("btnStartCalPh"), F("Calib"));

Se invece non si può fare, non sarebbe giusto fare anche per la parte F("Calib") tutto il discorso di utilizzare char?

La macro F() può essere utilizzata SOLO con stringhe costanti (quindi NON con variabili di tipo char array o altro) e con null'altro.

Guglielmo

Ok grazie per la precisazione. Nel caso specifico che stiamo guardando, io vado a scrivere effettivamente la parola "Calib", non sto passando una variabile, quindi potrei utilizzare la F( o sbaglio?

Portate pazienza, ma devo chiederlo, l'utilizzo della funzione "F(" fa sempre parte delle stringhe e quindi può portare al potenziale problema di riavvi oppure è una soluzione nei casi in cui devo spedire costanti?

Sono in confusione perchè nell'esempio di fabpolli

char parteRipetuta[14] = "btnStartCalPh"; //globale
char daInviare[24];
myNextion.setComponentText(parteRipetuta, F("Calib"));
strncpy(daInviare, parteRipetuta, strlen(parteRipetuta));
strncat(daInviare, F(".bco=1024"), 9);
strncat(daInviare, '\0', 1);
myNextion.sendCommand(daInviare);

lui ottimizza il discorso di utilizzare i char qui

myNextion.setComponentText([color=red]parteRipetuta[/color], F("Calib"));

ma dopo usa la funzione F(

myNextion.setComponentText(parteRipetuta, [color=red]F("Calib")[/color]);

bubba21:
Portate pazienza, ma devo chiederlo, l'utilizzo della funzione "F(" fa sempre parte delle stringhe e quindi può portare al potenziale problema di riavvi oppure è una soluzione nei casi in cui devo spedire costanti?

Stai confondendo le "stringhe" del 'C', che sono dei normali char array, con l'uso della classe "String" che è tutt'altro (e che si scrive con la 'S' maiuscola).

Guglielmo

Si hai ragione, intendevo la classe "String"..

La macro F() è solo per le stringhe classiche del ‘C’ (char array).

In pratica puoi SOLO usarla quando hai una situazione di F( “stringa_di_caratteri” ) ovvero una serie di caratteri racchiusi tra doppi apici.

Essa è definita come:

class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper*>(PSTR(string_literal)))

Guglielmo

Ok, perdonami ma faccio un copia per semplicità di quello che ho scritto prima togliendo le castronate che ho scritto.

L'utilizzo della funzione "F(" può portare al potenziale problema di riavvi oppure è una soluzione nei casi in cui devo spedire costanti?

Sono in confusione perchè nell'esempio di fabpolli

char parteRipetuta[14] = "btnStartCalPh"; //globale
char daInviare[24];
myNextion.setComponentText(parteRipetuta, F("Calib"));
strncpy(daInviare, parteRipetuta, strlen(parteRipetuta));
strncat(daInviare, F(".bco=1024"), 9);
strncat(daInviare, '\0', 1);
myNextion.sendCommand(daInviare);

lui ottimizza il discorso di utilizzare i char qui

myNextion.setComponentText(parteRipetuta, F("Calib"));

ma dopo usa la funzione F(

myNextion.setComponentText(parteRipetuta, F("Calib"));

La macro F() ha il solo scopo di ridurre l'occupazione della SRAM (sposta le strighe costati dalla SRAM alla Flash, liberando quindi la SRAM) e ... più SRAM hai libera, minore è la probabilità di avere problemi per esaurimento di essa.

Guglielmo