DigitalRead e PIND

Ciao a tutti. Utilizzo spesso PORT per scrivere sulle porte; stavolta, dopo aver scritto un lungo sketch usando digitalRead(5) ho pensato di usare PIND &0x20 per risparmiare sull'occupazione della flash che con l'IDE 1.6.11 su Windows 10 è dell'89%, mentre con l'1.6.9 su XP è del 95% e da problemi. Mettendo tutti PIN passa incredibilmente dal 95% al 57%, poiché lo utilizzo molto spesso per leggere lo stato del pulsante dell'encoder, ma non funziona più! Premendo il pulsante brevemente entra nel menu e ne riesce, mentre dovrebbe entrare nel menu solo tenendolo premuto per 1 secondo. Premendolo brevemente dovrebbe solo passare da una visualizzazione sul display a un'altra. Usando digitalRead(5), invece, funziona tutto come previsto. Qualcuno mi sa spiegare perché? Grazie.

Qualcuno mi sa spiegare perché?

Direi che hai un errore nello sketch. Ciao Uwe

La digitalRead() è una funzione complessa che fa molte cose, da qui la sua pesante occupazione nella flash, si parla di circa 80 byte per il primo uso all'interno della stessa funzione, se usata più volte all'interno della stessa funzione l'impegno di flash, per ogni uso, cala a 20-30 byte per ogni digitalRead(). Se accedi direttamente ai PORT degli AVR tramite i relativi registri e maschera serve una singola istruzione assembly per farlo, 2 byte, il che riduce notevolmente l'uso della flash se usi molte digitalRead(), si riduce notevolmente anche il tempo per leggere il dato, 125 ns contro 2-3 uS, il discorso è identico anche per la digitalWrite(). Puoi usare la flash fino all'ultimo byte disponibile, questo non crea nessun problema in run time, perché usando l'accesso diretto ai PORT il tuo codice da problemi è impossibile dirlo senza vederlo.

Questo è un pezzetto:

void loop()
{
P=PIND&B00100000; // Legge il pulsante dell'encoder (0=Premuto)
if(Po==1 && P==0) {t1=millis(); Po=0;} // Quando viene premuto il pulsante legge il tempo
if(Po==0 && P==0)     // Se era ed è premuto per almeno 1 secondo
  {                   // salta a Menu, poi ritorna ed esce.
  if(millis()-t1>999) // 
    {
    Po=1; Bip();
    detachInterrupt(0);         // Blocca gli interrupt per evitare che si accumulino conteggi
    Menu();             // che poi verrebbero divisi per un tempo brevissimo, non essendo
    while(PIND&0x20==0); // stata, nel frattempo, incrementata la variabile tempo.
    delay(200);
    Mask();
    attachInterrupt(0,ContaAB,FALLING); return;
    }
  } // END premuto per almeno 1 secondo[\CODE]

Con digitalRead(5) funziona. Qui ho provato sia con la notazione binaria che con quella esadecimale, ma non funziona. while(PIND&0x20==0) attende che venga lasciato il pulsante.

Mi sa che l'IDE 1.6.9. (su XP) ha qualche problema con sketch grandi, perché il display indica cose strane... Con 1.6.11 non ho problemi, così come non ho problemi se tolgo qualche parte dello sketch.

Lo sketch usa 30.902 byte (95%) dello spazio disponibile per i programmi. Il massimo è 32.256 byte. Le variabili globali usano 1.279 byte (62%) di memoria dinamica, lasciando altri 769 byte liberi per le variabili locali. Il massimo è 2.048 byte.

Ho tolto un blocco non essenziale e adesso sul display non appaiono più caratteri strani:

Lo sketch usa 30.412 byte (94%) dello spazio disponibile per i programmi. Il massimo è 32.256 byte. Le variabili globali usano 1.279 byte (62%) di memoria dinamica, lasciando altri 769 byte liberi per le variabili locali. Il massimo è 2.048 byte.

Non è che da qualche parte controlli se P == 1? Perché facendo la lettura in quel modo varrà o 0 o 0x20.

Comunque ti consiglio di cercare di scrivere codice più ordinato e leggibile/comprensibile. I commenti sono cosa buona e giusta, ma se devi metterne più in C di quanto si fa in assembly, c'è qualcosa che non va ;).

PS per astro: perché una chiamata a digitalRead() successiva alla prima dovrebbe richiedere 20/30 byte? Non basta richiamare la funzione, una volta che il compilatore l'ha inclusa? Mi aspetterei una RCALL (o eventualmente CALL) e un qualche salto (o Skip), nulla di più.

SukkoPera: PS per astro: perché una chiamata a digitalRead() successiva alla prima dovrebbe richiedere 20/30 byte? Non basta richiamare la funzione, una volta che il compilatore l'ha inclusa? Mi aspetterei una RCALL (o eventualmente CALL) e un qualche salto (o Skip), nulla di più.

A questa domanda non posso darti una risposta esaustiva, toccherebbe verificare il codice assembly per vedere cosa combina il compilatore, però se provi a inserire in uno sketch una singola digitalRead() vedrai che la dimensione del compilato aumenta tra 80-90 byte, se ne metti una seconda, all'interno della stessa funzione, il codice aumenta di 30-40 byte, se continui ad aggiungerne l'occupazione, per singola digitalRead(), cala progressivamente fino a pochi byte. Se metti le digitalRead() in funzioni diverse l'impegno della flash riparte da 50-60 byte per ogni funzione da dove la usi, quindi parte del codice già generato viene riutilizzato. La digital Read/Write è una funzione relativamente complessa che produce un codice specifico per ogni pin che leggi/scrivi, questo produce un "elevato" impegno di righe assembly ogni volta che la utilizzi, poi tocca vedere come il compilatore riesce ad ottimizzare più chiamate della stessa digitalRead/Write sullo stesso pin, cosa che dipende anche da come setti il livello di ottimizzazione, se è per la velocità sicuramente clona le righe di codice, così risparmia dei salti, se è per le dimensioni sicuramente cerca di riutilizzare al massimo il codice esistente. Non mi ricordo che livello di ottimizzazione usa l'IDE di Arduino quando compila.

SukkoPera: Non è che da qualche parte controlli se P == 1? Perché facendo la lettura in quel modo varrà o 0 o 0x20.

Infatti sarebbe bene aggiungere anche uno shift a destra di 5 posizioni per ottenere sempre solo 0 o 1, sopratutto se P è una variabile bool, poi c'è la questione dei rimbalzi dello switch meccanico che non vedo gestiti in nessun modo.

astrobeed: ... rimbalzi dello switch meccanico ...

... la prima cosa che ho pensato, prima di arrivare a leggere anche il tuo post :D

Pero' lui non ci dice se fa il debounce hardware (che mi sembrerebbe il solo modo pratico, se deve gestire il pulsante in modo che faccia cose diverse con pressioni diverse, e quindi gli causerebbe problemi farlo in modo software con i famigerati delay ... :D)

Mah... Conosco bene il problema di rimbalzi meccanici, ma sono riuscito a evitarli senza faticare molto. Di solito metto un delay di 200mS nel while() che attende che venga lasciato il pulsante.

... e fare come fanno tutti i professionisti e le ditte ? ... cioe' usare una resistenza ed un condensatore e togliersi il problema a livello software ? ... ;)

Uuuhhh!!!! Certo!!! Facendo & ottengo 32 oppure 0!!!

Così funziona:
(PIND&0x20)>>5, equivalente a digitalRead(5): lascia solo il 5° bit e poi lo sposta a destra di 5 posizioni.
però con IDE 1.6.11 su Windows 10 lo sketch occupa l’88% di Flash, quindi risparmio appena l’1%.

Non ho capito perché non funziona così:
(PIND>>5)&1
Dovrebbe essere la stessa cosa, in quanto prima fa scorrere a destra di 5 posizioni e poi fa AND con 1, quindi legge solo l’LSB

In effetti dovrebbe funzionare. Sicuro di aver messo le parentesi come hai scritto?

Consentimi però un'altra osservazione: stai confondendo un test LOGICO (il cui risultato è solo vero o falso) con un test ARITMETICO (il cui risultato è un numero). Nel primo caso, è bene scrivere gli if tipo:

if (pippo) {
  if (!pluto) {

Ovvero evidenziando chiaramente fin dal modo in cui scrivi il codice il fatto che non ti interessa un valore numerico preciso, ma solo il suo significato logico.

Oltre alla maggior chiarezza, questo [u]ti farà anche risparmiare flash[/u], potendo evitare l'AND, il caricamento di una costante, il confronto, ecc...

Occhio che PIND è una macro C che viene risolta in un accesso diretto al registro mappato in memoria ram. Tuttavia il compilatore decide come accedere ai registri, tra accesso diretto o tramite IO, in sostanza cambia l'istruzione assembler.

Il registro PIND è sia leggibile che scrivibile, per cui quella operazione modifica PIND, mentre questa dovrebbe funzionare: ( (PIND) >> 5 ) & 1

In ogni caso conviene sempre consultare il datasheet e purtroppo sono 6 mesi che non lo apro.

Ciao.

Sukkopera:

stai confondendo un test LOGICO (il cui risultato è solo vero o falso) con un test ARITMETICO (il cui risultato è un numero).

No, perché faccio while((PIND&0x20)>>5==0) {delay(200)} per attendere che il pulsante dell'encoder venga lasciato e while((PIND&0x20)>>5==1); per attendere che venga premuto (chiude a massa).

Funzionano anche while((PIND&0x20)==0) {delay(200)} per attendere che il pulsante dell'encoder venga lasciato e while((PIND&0x20)==0x20); per attendere che venga premuto (chiude a massa), ma l'occupazione di memoria sta sempre all'88%: il guadagno è insignificante.

MauroTec:

Il registro PIND è sia leggibile che scrivibile, per cui quella operazione modifica PIND, mentre questa dovrebbe funzionare: ( (PIND) >> 5 ) & 1

Perdonami, ma non capisco... Perché lo modifica? Perché metti PIND da solo fra parentesi? Se no farebbe D>>5???

Perdonami, ma non capisco… Perché lo modifica?
Perché metti PIND da solo fra parentesi? Se no farebbe D>>5???

Non dovrebbe, in quanto occorre una assegnazione per modificare PIND, però magari quella sintassi viene erroneamente tradotta dal compilatore C++ e allora siamo in presenza di un BUG. Tocca vedere il listato generato dal preprocessore per vedere come viene risolta la macro PIND, magari è colpa sua.

Questa istruzione C:

DDRC &= ~_BV(PC0);

viene risolta dal preprocessore così:

(*(volatile uint8_t *)((0x07) + 0x20)) &= ~(1 << (0));

Se anche tu vuoi vedere il lavoro svolto dal preprocessore devi aggiungere save-temps come parametro di compilazione, mi pare L’ide Arduino ora permetta di fare ciò.

L’opzione save-temps mantiene tutti i file temporanei creati durante il processo di compilazione, pertanto avrai un file .i (preprocessore) e un .s (assembler).

Il registro PIND è sia leggibile che scrivibile, per cui quella operazione modifica PIND, mentre questa dovrebbe funzionare: ( (PIND) >> 5 ) & 1

Avrei dovuto usare “potrebbe” al posto di “dovrebbe”.

Ciao.

Datman: No, perché faccio while((PIND&0x20)>>5==0) {delay(200)} per attendere che il pulsante dell'encoder venga lasciato e while((PIND&0x20)>>5==1); per attendere che venga premuto (chiude a massa).

Funzionano anche while((PIND&0x20)==0) {delay(200)} per attendere che il pulsante dell'encoder venga lasciato e while((PIND&0x20)==0x20); per attendere che venga premuto (chiude a massa), ma l'occupazione di memoria sta sempre all'88%: il guadagno è insignificante.

Un conto è quel che funziona, un conto è quel che è più leggibile. Comunque qua siamo in un caso un po' borderline, per cui fai come credi. Però ti dico che con:

while (PIND & 0x20)
  delay (200);

e

while (!(PIND & 0x20))
  delay (200);

Potreti risparmiare qualche byte. Effettivamente saranno sì e no una decina, per cui effettivamente una quantità insignificante. Però forse puoi togliere anche il delay(), e risparmiare ancora qualcosa.

Per quanto dice Mauro, effettivamente quella riga non modifica niente. Se ci fosse un bug così grande nel compilatore se ne parlerebbe ovunque ;). E soprattutto bisognerebbe rivedere pesantemente i test di GCC...