Parere in merito al layout del flusso del programma

Salve a tutti,

essendo io neofita (al massimo 2 settimane di autoapprendimento!) in programmazione C (e quindi di Arduino) ma avendo fatto uso di altri MCU con programmazione BASIC e derivati (Basicx Bx24 prima, e PICAXE poi) la mia domanda riguarda come sarebbe preferibile organizzare un programma in C di ARDUINO, mi spiego meglio:

Nei PICAXE, per razionalizzare il programma, lo suddivido in diversi "sub programmi" chiamati da una Label, in mancanza di routine particolari alla fine di ogni sub, il flusso del programma continua in modo sequenziale, a meno di non bloccarlo con qualche comando, se mi serve chiamare o spostare il flusso verso una sub particolare, semplicemente la chiamo con un "goto" (se non deve tornare) o con un "gosub" (se deve tornare alla routine che lo ha invocato. Se viene ben impostato, è molto utile per creare un certo ordine che servirà in secondo momento quando si ritornerà per apportare modifiche.

Adesso, la mia domanda è, visto che in C esiste una parte del codice rinchiusa dentro una "sub" (o pseudo tale, chiamata Function giusto?) ovvero la: void loop() quindi non è necessario fare goto main come si fa usualmente con i picaxe. ho ben capito che se metto tutto dentro questa Function eseguirà all'infinito il programma. Fin qui tutto ok, ma per programmi più complessi, devo creare delle alte functions (sub programmi) che le invoco cosi: nomefunction(); questo è l'equivalente di un "gosub" perché una volta terminato la Function invocata, il flusso del programma ritorna di nuovo al punto dove è stato invocato, e fin qui, nessun problema.

La mia domanda è cosa sia più corretto fare per dare al programma un flusso corretto e veloce, vi faccio degli esempi perchè non so spiegarlo a parole:

Caso 1:
void loop(){

comando 1;//esegue qualsiasi cosa...
comando 2;
comando 3;
AltraSub();//chiama una function...
}

void AlraSub(){

comando 4;
comando 5;
}

Caso 2:

void loop(){

Sub1();
Sub2();
Sub3();
Comando 4;
Comando 5;

}

Sub1(){ ... }
Sub2(){ ... }
Sub3(){ ... }
ecc...

spero di essere stato abbastanza chiaro, nel primo caso, il flusso del programma e in gran parte contenuto dentro "loop" con una chiamata ad una routine esterna a loop, finita questa, il flusso ritorna al punto dove è stata chiamata.

Nel secondo caso, nel "loop" vi sono solo le chiamate alle varie Functions o sub ogni volta che una di queste functions ha terminato ,il flusso ritorna al punto dove è stato invocando e continua al prossimo comando.

Dal punto di vista strutturale , io da profano direi che poco o niente cambia, ma non so se è così, attualmente io vedo più eficiente il caso 2 ma non vorrei che facendo così andassi a peggiorare le prestazioni di esecuzione, specialmente considerando che ho intenzione di imparare al meglio e tentare di realizzare dei progetti abbastanza complessi e non vorrei creare dei "mostri" che non rendono al meglio, già adesso tento di meccanizzare le cose ripetitive con l'uso estensivo di cicli FOR per esempio, ma c'è tanto ancora da imparare!!!

A voi esperti la parola!!

Colgo l'occasione per augurarvi UN PROSPERO ANNO NUOVO!!

Antonio

C'è un mio post riguardo alla programmazione in C, ma non ricordo dove si trova, comunque approfitto della tua domanda per ribadire i concetti base.

La funzioni loop() è obligatori, questa funzione non termina mai o meglio quando termina viene richiamata internamente. Se questa funzione non esistesse potresti in C puro usare un while (1) { }. Essendoci un solo loop e non essendo possibile programmare in multithread conviene sempre sfruttare questo loop anziché crearne altri dentro le funzioni. Le funzioni utente possino creare loop ma è meglio che questo loop impieghi un tempo definito e compatibile con le funzionalità che il programma deve fornire. Supponiamo di leggere dei dati dalla seriale, la lettura deve essere effettuata nella funzione loop e in base al carattere ricevuto si chiamano le funzioni accessorie. Se una delle funzioni accessorie crea un loop lungo che dura x tempo, per questo x tempo non sarà possibile processare i dati in arrivo dalla seriale, se questo è compatibile con le funzionalità che il programma deve svolgere si possono creare loop lunghi nelle funzioni.

Se il programma deve ricevere un pacchetto dati e terminata la ricezione non è necessario più l'interfaccia con l'utente (tramite seriale) si può chiamare una funzione che ha un loop lungo al fine di processare i dati letti. Mentre se è necessaria una certa interattività con l'utente (tramite seriale o altro protocollo) è necessario fare in modo che sia il loop principale a processare i dati, in questo caso le funzioni chiamate processerano i dati sfruttando la funzione loop().

Efficienza:
Per adesso non ti devi preoccupare della velocità di esecuzione ma dell'efficienza. Quindi non usare una variabile (int) se il contenuto rientra nel tipo (byte), stessa cosa per float o double. Non creare array globali se una volta usati non ti servono più, in questo caso e meglio creare una funzione e dentro dichiarare l'array, questo sarà allora un tipo di dato locale visibile solo alla funzione e al termine di questa tutti i nomi di variabile locali vengono cancellati liberando memoria. Dopo avere realizzato tutte le funzionalità previste ti poni il problema della velocità di esecuzione e ci sono buone probabilità che il problema non si ponga del tutto in quanto il codice risponde con tempistiche accettabili, quindi non fasciarti la testa per paura di sbatterla.

Il codice che scrivi se non va bene lo puoi buttare e stai tranquillo che non inquini, con questo voglio di che se hai scritto un codice che non ti piace e sai come corregerlo, riparti da capo punto per punto, prendi quello che ti sembra buono dal vecchio codice e riparti. Si lo so il programmatore è pigro, ma nella fase di apprendimento non c'è spazio per la pigrizia e per l'insuccesso. Tranquillo il codice scritto la seconda volta anche se fa schifo lo farà sempre meno di quello precedente (quasi sempre).

Spezzetta le funzionalità nelle parti più piccole che riesci ad immaginare e poi accorpa alcune di esse nelle funzioni. Non è lampante ma chiamare una funzione chiamante equivale a ricorsione la quale dovrebbe avere come conseguenza l'esaurimento dello stack. Dico dovrebbe perchè ho letto un post dove la ricorsione sembra non avere effetti nefasti. Nel post in questione si ipotizzava che il compilatore fosse in grado di accorgersi della ricorsione e ripulire lo stack mentenedo solo il numero di ricorsione, così da ritornare indietro alla fine.

Ops mi sono lasciado prendere la mano e ho scritto cose non proprio comprensibile a chi sta ancora studiando il C.

Buon coding a tutti.

Grazie MauroTec per la celere risposta, sei stato molto chiaro e gentile, non preoccuparti per il dilungarti in dettagli "hard" per fortuna sono a digiuno solo di C, ho alcuni anni di pratica (non grandissima comunque) con Basic e quindi comprendo i problemi che mi hai proposto, infatti era di queste cose che mi riferivo, avere tante fucntions o averne poche cosa cambia per la gestione dello stack? adesso sto scrivendo un programma e sto dividendo tutto i tante functions che sono chiamate una dietro l'altra in "void loop()" invece di accodare tutto in un'unico grande "codicione!" mi è più facile da comprendere a colpo d'occhio e mi risulta anche più ordinato, poi ogni function viene chiamata all'interno di "void loop()" cosi:

void loop(){

function1();
function2();
ecc...
}

void Function1(){ //il codice da eseguire}

void Fucntion2(){ //il codice da eseguire }

void Function3(){ //il codice da eseguire}
ecc...

Credi che possa andare bene?

Ciao e di nuovo grazie per l'aiuto. BUON ANNO!!

Antonio

La scelta se creare per un dato gruppo di funzioni una funzione sua dipende sopratutto da quante volte chiami quel gruppo di funzioni.
Se per esempio Ti crei un modo particolare per stampare un carattere su un display e lo fai in 2 posti diversi del Sketch allora é meglio farne una funzione e chiamarla 10 volte.
Cambia poco la velocitá di esecuzione e non c'é problema di Stack. Anche perché il compilatore fa delle ottimizzazioni che non conosco neanch io.

Ciao Uwe

hiperformance71:
queste cose che mi riferivo, avere tante fucntions o averne poche cosa cambia per la gestione dello stack?

in teoria la chiamata (non la funzione in se) fa si che il processore si salvi appunto nell'area di stack quantomeno l'indirizzo di ritorno e questo fa si che le funzioni ricorsive (ossia una funzione che richiama se stessa continuamente) tendano ad occupare parecchia di questa memoria delle volte crashando il sistema... i ragazzi qua però hanno scoperto che stranamente il compilatore dell'arduino se ne accorge (forse solo a volte non so) e fa un abracadabra risparmiando memoria.

quanto al tuo programma non mi porrei la domanda sul quante volte dividere il loop in sottoprogrammi... io divido quando ho dei sottoprogrammi che fanno una cosa inerente... ossia se ho da gestire non so la temperatura, un lcd, dei tasti, faccio 3 sottoprogrammi uno per la temperatura uno per lcd uno per i tasti... se dopo quello dei tasti è lungo 100 righe e quello della temperatura sono 4 istruzioni non mi cambia la vita.

il mio consiglio è di studiare per bene come funziona il passaggio dei parametri e le dichiarazioni locali delle variabili che sono un po' la differenza tra i vecchi basic alla goto e questi linguaggi. esiste il goto anche in c ma non lo usa nessuno perche non serve ma soprattutto e' considerato il male supremo della programmazione razionale (imho invece rarissimamente ma delle volte aiuta)
invece utilissima pratica spesso trascurata è quella del commento di cosa fa una procedura... che poi delle volte se le variabili e le procedure hanno nomi mnemonici basta anche una riga di asterischi per delimitare una zona di programma che fa qualcosa da una zona di programma che fa un'altra cosa...sembra na stupidata ma il ns cervello se vede i blocchetti "alla lego" ragiona meglio...

qsecofr:

hiperformance71:
queste cose che mi riferivo, avere tante fucntions o averne poche cosa cambia per la gestione dello stack?

in teoria la chiamata (non la funzione in se) fa si che il processore si salvi appunto nell'area di stack quantomeno l'indirizzo di ritorno e questo fa si che le funzioni ricorsive (ossia una funzione che richiama se stessa continuamente) tendano ad occupare parecchia di questa memoria delle volte crashando il sistema... i ragazzi qua però hanno scoperto che stranamente il compilatore dell'arduino se ne accorge (forse solo a volte non so) e fa un abracadabra risparmiando memoria.

quanto al tuo programma non mi porrei la domanda sul quante volte dividere il loop in sottoprogrammi... io divido quando ho dei sottoprogrammi che fanno una cosa inerente... ossia se ho da gestire non so la temperatura, un lcd, dei tasti, faccio 3 sottoprogrammi uno per la temperatura uno per lcd uno per i tasti... se dopo quello dei tasti è lungo 100 righe e quello della temperatura sono 4 istruzioni non mi cambia la vita.

il mio consiglio è di studiare per bene come funziona il passaggio dei parametri e le dichiarazioni locali delle variabili che sono un po' la differenza tra i vecchi basic alla goto e questi linguaggi. esiste il goto anche in c ma non lo usa nessuno perche non serve ma soprattutto e' considerato il male supremo della programmazione razionale (imho invece rarissimamente ma delle volte aiuta)
invece utilissima pratica spesso trascurata è quella del commento di cosa fa una procedura... che poi delle volte se le variabili e le procedure hanno nomi mnemonici basta anche una riga di asterischi per delimitare una zona di programma che fa qualcosa da una zona di programma che fa un'altra cosa...sembra na stupidata ma il ns cervello se vede i blocchetti "alla lego" ragiona meglio...

Si, anch'io lo faccio così, suddivido il programma nei blocchi che ha, almeno quelli che ritengo richiedano una sub tutta loro, cose del tipo:

void Salva_in_EEPROM(){ ... } //function che salva i dati in EEPROM
void Check_EEPROM(){ ... } //function che controlla i dati in EEPROM se sono coerenti...
void Lettura_ADC_Channels(){ ... } //lettura dei pin analogici...
void LCD_out(){ ... } //routine che visualizza su LCD i dati raccolti/calcolati...
ecc...

Per fortuna tendo a documentare abbastanza ogni riga di codice e cosa fa, avendo qualche problemino di memoria mi torna molto ultile, avvolte su un progetto ci ritorno dopo mesi, se non ho commentato tutto, poi sono dolori per riprendere il flusso del programma!!

Per la ricorsività credo di non avere il problema, o almeno spero, credo di averne fatto un pò il callo in basic con i gosub, per esempio nei picaxe sono ammessi un massimo di 256 cicli di gosub ricorsivo e poi il compilatore va in errore, quindi una volta capito come fare, credo di poter evitarlo anche in C. A meno che non abbia ancora capito come lo si fa in C.

Per ora non posso neanche testare i codici che ho creato fino ad ora (mi correggo, alcuni li ho testati sul simulatore di arduino) perchè causa feste natalizie, il corriere non è ancora venuto a portarmi il mio nuovo ARDUINO UNO SMD, pensavo di giocarci questi ultimi giorni del 2012!! ed invece dovrò aspettare all'anno nuovo!! Quindi sto qua a tentare di assorbire come una spugna ogni consiglio utile ad imparare da chi lo sa fare meglio di me.

ciao,
Antonio

Mi accodo al ragionamento di Uwe e ti consiglio anch'io di non esagerare con lo spezzettamento del tuo programma in funzioni inutili.
Cioè, se una certa azione la esegui solo 1 volta per ciclo del loop, è inutile infilarla in una sub-routine. Oltre ad aumentare i dati creati in RAM, aumenti anche la dimensione dello sketch nella Flash visto che il compilatore deve gestire salti e ritorni vari.

Se hai un programma in cui devi fare tante volte la stessa azione magari su risorse differenti, allora è vantaggioso crearsi una funzione generica che accetta in ingresso i parametri necessari all'azione da svolgere, altrimenti conviene mettere tutto nel loop.

leo72:
Cioè, se una certa azione la esegui solo 1 volta per ciclo del loop, è inutile infilarla in una sub-routine. Oltre ad aumentare i dati creati in RAM, aumenti anche la dimensione dello sketch nella Flash visto che il compilatore deve gestire salti e ritorni vari.

Non sono d'accordo.
Il compilatore dovrebbe accorgersi che la funzione è chiamata solo una volta e credo che la "ricopi" nel ciclo principale. Infatti il codice non aumenta neanche di 1 byte.

PaoloP:

leo72:
Cioè, se una certa azione la esegui solo 1 volta per ciclo del loop, è inutile infilarla in una sub-routine. Oltre ad aumentare i dati creati in RAM, aumenti anche la dimensione dello sketch nella Flash visto che il compilatore deve gestire salti e ritorni vari.

Non sono d'accordo.
Il compilatore dovrebbe accorgersi che la funzione è chiamata solo una volta e credo che la "ricopi" nel ciclo principale. Infatti il codice non aumenta neanche di 1 byte.

Capito male avesti.
Se nel corso del programma scrivi 10 bitwise di seguito all'altra e questo continui a farlo in tante porzini del codice e evidente che devi metterle tutte nella funzione, così il codice non si ingrassa ed è molto più leggibile.

Stessa cosa se chiami 10 volte una funzione il codice aumenti di dimensioni rispetto a quando la chiami una sola volta, ma in questo caso non c'è alternativa, se devi stampare con print in 10 posti diversi non puoi fare una funzione.

Comunque il concetto è chiaro.

PaoloP:

leo72:
Cioè, se una certa azione la esegui solo 1 volta per ciclo del loop, è inutile infilarla in una sub-routine. Oltre ad aumentare i dati creati in RAM, aumenti anche la dimensione dello sketch nella Flash visto che il compilatore deve gestire salti e ritorni vari.

Non sono d'accordo.
Il compilatore dovrebbe accorgersi che la funzione è chiamata solo una volta e credo che la "ricopi" nel ciclo principale. Infatti il codice non aumenta neanche di 1 byte.

Il compilatore non è d'accordo con te :stuck_out_tongue:
Crea un piccolissimo sketch con un paio di semplici operazioni ed infilale nel loop. Compila e guarda la dimensione dello sketch.
Poi crea una sub-routine, infila quelle operazioni nella sub-routine e richiamala dal loop. Compila e guarda la dimensione dello sketch.
Anche se chiamata 1 sola volta, vedrai che la dimensione finale è leggermente maggiore (6 byte).

leo72:

PaoloP:

leo72:
Cioè, se una certa azione la esegui solo 1 volta per ciclo del loop, è inutile infilarla in una sub-routine. Oltre ad aumentare i dati creati in RAM, aumenti anche la dimensione dello sketch nella Flash visto che il compilatore deve gestire salti e ritorni vari.

Non sono d'accordo.
Il compilatore dovrebbe accorgersi che la funzione è chiamata solo una volta e credo che la "ricopi" nel ciclo principale. Infatti il codice non aumenta neanche di 1 byte.

Il compilatore non è d'accordo con te :stuck_out_tongue:
Crea un piccolissimo sketch con un paio di semplici operazioni ed infilale nel loop. Compila e guarda la dimensione dello sketch.
Poi crea una sub-routine, infila quelle operazioni nella sub-routine e richiamala dal loop. Compila e guarda la dimensione dello sketch.
Anche se chiamata 1 sola volta, vedrai che la dimensione finale è leggermente maggiore (6 byte).

Si, hai ragione, ho fatto la prova con un semplice codice per verificare, ed effettivamente ci sono 6 byte in più se viene chiamata una funzione da loop, quindi forse devo rivedere qualche cosa, altrimenti mi ciuccia troppa RAM sta cosa! Hahahaaa, per ora non ci sono problemi apparenti, ma i programmi, mentre impariamo nuove cose crescono a dismisura perchè vogliamo includere le nuove conoscenze! quindi è facile che dopo tanto scrivere e provare, debuggare e riscrivere per ottimizzare un po, si finisce per incasinale il povero Ardu! Spero di tenere amente queste utilissime info dell'arte di programmare (per ora sono l'apprendista dell'apprendista dell'artista!! Haha!)

Ho provato anche a vedere la differenza tra dichiarare le variabili Global (fuori da setup() e loop() ) e dentro loop() e la fifferenza era apprezzabile, 600 bytes global, 466 bytes dentro loop()!!

Spero di non andare OT, perdonatemi se è così, Qualcuno mi può spiegare come passare le variabili? credo sia quando si fa:

void MiaRoutine(int var){ ... } dovrebbe essere quando metto tra le parentesi "int var" ma ancora non ho afferrato come avviene.

--> Funzioni in C: void, double e le altre | Guida C | Programmazione HTML.it

hiperformance71:
Si, hai ragione, ho fatto la prova con un semplice codice per verificare, ed effettivamente ci sono 6 byte in più se viene chiamata una funzione da loop, quindi forse devo rivedere qualche cosa, altrimenti mi ciuccia troppa RAM sta cosa!

Attento a non confondere FLASH ed RAM (ed EEPROM).
Con i microcontrollori si ha a che fare con un'architettura informatica di tipo Harvard, dove la memoria che contiene il codice è separata dalla memoria in cui vengono create e gestite le variabili, ossia i dati generati dal programma.

La Flash contiene il codice, ed è una memoria non volatile riscrivibile: non essendo volatile, mantiene il tuo programma anche se stacchi l'alimentazione. La RAM è volatile per cui, una volta staccata l'alimentazione, tutti i dati elaborati vanno persi. Per contro, ha la possibilità di essere scritta un numero praticamente infinito di volte, mentre la Flash ha un limite di 10.000 riscritture.

Quando compili uno sketch e vedi 6 byte in più per il salto alla tua sub-routine (o funzione, chiamala come ti pare), si parla di memoria FLASH consumata in più. Il consumo della RAM non lo vedi da lì ma sappi che comunque cresce anche quello perché un salto prevede il salvataggio del punto di ritorno nello stack del programma, che è gestito in RAM.

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly: 
  int a = 3;
  int b = 5;
  int c;
  c = a * b;
}

Dimensione del file binario dello sketch: 466 bytes (su un massimo di 32.256 bytes)

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly: 
  int a = 3;
  int b = 5;
  int c;
  c = moltiplica(a, b);
}

int moltiplica (int x, int y)
{
return x * y;
}

Dimensione del file binario dello sketch: 466 bytes (su un massimo di 32.256 bytes)

Non consuma FLASH perché il compilatore ottimizza il codice, ma consuma lo STACK per la chiamata alla funzione. o no?

void setup() {
    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);
}

void loop() {}

876 byte compilato.

void setup() {
    pinMode(13, OUTPUT);
    accendiLed();
}

void loop() {}

void accendiLed() {
    digitalWrite(13, HIGH);
}

882 byte compilato.

Forse il tuo caso è visto in modo differente dal compilatore perché il fatto che ci sia un valore da restituire probabilmente spinge il compilatore ad estrarre la funzione interna alla sub-routine, sostituendola alla chiamata.
Ma nel mio caso non do un valore da restituire, il compilatore non sa cosa succeda nella sub-routine, per cui è costretto a chiamarla e ad eseguirla.

leo72:

hiperformance71:
Si, hai ragione, ho fatto la prova con un semplice codice per verificare, ed effettivamente ci sono 6 byte in più se viene chiamata una funzione da loop, quindi forse devo rivedere qualche cosa, altrimenti mi ciuccia troppa RAM sta cosa!

Attento a non confondere FLASH ed RAM (ed EEPROM).
Con i microcontrollori si ha a che fare con un'architettura informatica di tipo Harvard, dove la memoria che contiene il codice è separata dalla memoria in cui vengono create e gestite le variabili, ossia i dati generati dal programma.

La Flash contiene il codice, ed è una memoria non volatile riscrivibile: non essendo volatile, mantiene il tuo programma anche se stacchi l'alimentazione. La RAM è volatile per cui, una volta staccata l'alimentazione, tutti i dati elaborati vanno persi. Per contro, ha la possibilità di essere scritta un numero praticamente infinito di volte, mentre la Flash ha un limite di 10.000 riscritture.

Quando compili uno sketch e vedi 6 byte in più per il salto alla tua sub-routine (o funzione, chiamala come ti pare), si parla di memoria FLASH consumata in più. Il consumo della RAM non lo vedi da lì ma sappi che comunque cresce anche quello perché un salto prevede il salvataggio del punto di ritorno nello stack del programma, che è gestito in RAM.

Si, quello che spiegni lo sapevo già, ma non conoscendo come misurare o stimare la RAM in uso da una MCU ho assunto che se per un semplice spostamento di variabile, in FLASH guadagno o perdo 100bytes è da sperarsi che anche in RAM vi siano queste differenze anche se non si conosce la loro entità anche perché, se le variabili sono global, sono fissate nella RAM durante tutto il tempo che c'é alimantazione giusto? mentre se vengono dichiarate in funzioni, saranno variabili locali ed il sistema le userà solo mentre servono, uscito dalla funzione le cancellerà o renderà lo spazio occupato da questi dati libero di essere usato in un'altro posto del codice, giusto?
Approposito di limite di scritture della memoria interna (EEPROM in questo caso), ho visto un filmato su youtube di una prova fatta da alcuni ragazzi per collaudare quante riscritture durasse prima di dare errore, hanno creato un loop con contatore, dopo un po di tempo (non ricordo quanto) si è bloccato, facendo qualcosa come 126000 riscritture se non ricordo male! interessante (quindi da tenere ben a mente quando si fanno dei loop che scrivono in EEPROM ma anche in FLASH, si rischia di rovinare la memoria!).

Grazie per il link al riguardo il passaggio di variabili, molto ben spiegato, ad una lettura veloce ho capito abbastanza come funziona, adesso me lo studierò per bene per capirlo al meglio e magari riscrivere qualche riga dei programmi che sto facendo per vedere quanto miglioriamo nel risparmio di prezionsa FLASH e sperando che altrettanto ancora più preziona RAM che non è mai abbastanza!!

CIAO!! :wink:

hiperformance71:
se le variabili sono global, sono fissate nella RAM durante tutto il tempo che c'é alimantazione giusto?

Giusto. Tutto il programma le vede e può accedervi e "vivono" finché l'Arduino funziona.

mentre se vengono dichiarate in funzioni, saranno variabili locali ed il sistema le userà solo mentre servono, uscito dalla funzione le cancellerà o renderà lo spazio occupato da questi dati libero di essere usato in un'altro posto del codice, giusto?

Sì. La memoria liberata da una variabile locale può essere riusata in un altro punto del programma.

Approposito di limite di scritture della memoria interna (EEPROM in questo caso), ho visto un filmato su youtube di una prova fatta da alcuni ragazzi per collaudare quante riscritture durasse prima di dare errore, hanno creato un loop con contatore, dopo un po di tempo (non ricordo quanto) si è bloccato, facendo qualcosa come 126000 riscritture se non ricordo male! interessante (quindi da tenere ben a mente quando si fanno dei loop che scrivono in EEPROM ma anche in FLASH, si rischia di rovinare la memoria!).

Atmel dà la EEPROM interna per max 100.000 riscritture, oltre tale valore può darsi che si riesca a scrivere senza errori come no.

L'importante è comprendere che una funzione è un blocco e tutto quello che è un blocco può creare variabili locali terminato il blocco queste variabili terminano di esistere.

Il ciclo for è un blocco, la if è un blocco, il while è un blocco, ma anche {} è un blocco, quindi ad esempio:

loop() {
// sono dentro il blocco loop, quello che creo qui viene distrutto e ricreato ogni volta che la funzione viene rieseguita.

{
//questo è un blocco quello che creo qui è locale e viene distrutto al termine del blocco.
}

}

Creare variabli nella funzione loop comporta un lavoro di stack continuo, quindi non esagerate, anzi non ha senso creare variabili locali qui.
Se per comodità volete dichiarare delle variabili nella funzione loop così da averle a portata di occhio anteponete al tipo la parola chiave
static, in questo modo la variabile è visibile solo all'interno della funzione loop ma questa viene creata una sola volta e quindi il valore contenuto viene
mantenuto proprio come per una variabile globale.

Ciao.
Ciao.