Multistrumento portatile per piccoli valori

Questo post, come anticipato in spam-bar, e' per discutere della costruzione di uno strumentino portatile (formato tester), in grado di misurare quantita' abbastanza piccole (fino ai microvolt, nanoampere e microohm, forse di piu, da vedere), in modo abbastanza facile e decentemente preciso (anche se non sara' uno strumento da laboratorio)

L'idea e' nata dal fatto che tre diversi strumenti che avevo reallizzato una ventina di anni fa (analogici, all'epoca le MCU costavano troppo, e peraltro io non me ne ero ancora interessato), hanno fatto negli anni una brutta fine, per cui mi era presa l'idea di rifarmi il milliohmetro migliorandolo ... poi la sindrome di Gundam ha iniziato ad uscire dallo scatolone in cui l'avevo relegata ed ha iniziato a suggerirmi modifiche ed integrazioni (ma se la misura di resistenza e' basata sulla misura della tensione ai suoi capi quando ci scorre dentro una corrente nota, allora perche' non misurare anche le tensioni ? ... e gia che ci siamo, perche' non collegare in parallelo ai terminali di misura una resistenza nota e misurarci pure le correnti ? ... e se poi la scala base non basta, perche' non farne due ? ... e ...) ... ed a questo punto ho dato una botta in testa alla sindrome di Gundam e l'ho risigillata nel suo scatolone, che qui gia avevo rifatto lo stampato 4 volte.

Inizialmente l'idea era di usare degli INA, ma nessuno che avesse prezzi decenti aveva anche le caratteristiche che volevo (e poi la complessita' circuitale avrebbe aumentato i componenti, e quindi il rumore), cosi ho preso in considerazione gli ADC che si usano di solito per le celle di carico, e dopo averne scartati alcuni, ho trovato gli ADS1232 ... il datasheet riporta 3.5nA di corrente di ingresso, filtro digitale interno, 10 letture per secondo (piu che sufficenti), e diverse altre caratteristiche che mi hanno fatto decidere di provare ad usarlo (non ultimo il fatto che costano intorno ai 6 Euro l'uno, contro i 30 o 40 di altri modelli con caratteristiche simili)

In teoria, senza considerare errori e rumore, con 24 bit ed il PGA impostato a 2 (fondoscala 1.25V), la risoluzione per ogni bit dovrebbe essere di 74.5 nV, mentre con il PGA impostato a 128, (fondoscala di 19.5mV) avrei una risoluzione per bit di 1.2nV, ma in pratica sono piu ridotte, sia per via del rumore che dei disturbi (ed anche della conversione interna dell'ADC)

Secondo il datasheet, con il PGA a 2 (fondoscala a +/-1.25V), la risoluzione effettiva e' di 23.5bit (consideriamone 23, che non ho nessuna voglia di star li ad affettare i bit), che mi da' una risoluzione effettiva di 149 nV per bit, mentre quella "noise-free" e' di 21.4 (21), che mi da' una risoluzione effettiva di 596 nV per bit ... mentre con il PGA impostato a 128 (fondoscala di +/- 19.5mV), la risoluzione effettiva e' di 21.1bit (21), che da una risoluzione effettiva di 9.3nV, e quella "noise-free" e' limitata a 18.4bit (18), dove avrei una risoluzione "priva di rumore" di 74.4nV (ma va testato se ne vale la pena ... in fondo, a quanti potrebbe mai servire di misurare nanovolt con uno strumento portatile, sul campo ? sono perfino indeciso se aggiungerla, questa scala, nel software, perche' ricordiamoci che questo non e' uno strumento da laboratorio, ma solo una specie di "tester" portatile per valori molto piccoli)

Dato che lo stampato ancora mi deve arrivare, e non posso certo montare uno di quei "cosi" su una millefori, per il momento la discussione verte piu che altro sul come riuscire a tirare insieme un programma decente, che funzioni su un 328PB (PB perche' ne ho in casa un po), e creare le necessarie funzioni per gestire tutto quanto.

La lettura dell'ADC non dovrebbe essere un problema (almeno in teoria), non servono librerie, ha un'interfaccia "pseudo-SPI", serve semplicemente all'accensione dargli una precisa sequenza di impulsi con il pin PWDN per inizializzarlo, poi metterlo alto, lui inizia a leggere, e quando i dati sono pronti manda basso il pin DATA, a quel punto gli si mandano 24 impulsi sul pin CLK e si leggono i 24bit uno alla volta, poi con un ulteriore impulso di clock si "resetta" il pin DATA, che si rimette alto finche' non ci sono altri dati da leggere, e cosi via ... con questa modalita' di funzionamento, non servono temporizzazioni critiche o gestione del flusso dati complicata, si puo fare in modo che sia direttamente l'ADC a controllare il programma della MCU ... nel loop si controla lo stato di DATA, quando va basso, si chiama una funzione che legge, carica tutto in un buffer, fa la media del buffer, poi si chiamano altre funzioni per fare i calcoli, formattare le stringhe in base al tipo di lettura che si sta eseguendo, aggiornare il display, e poi si torna a controllare lo stato del pin ed a leggere se qualche pulsante e' stato premuto, siccome il ciclo si ripete ogni 100mS, dopo la lettura e la media dovrebbe rimanere tempo in abbondanza per fare tutto il resto.

Quindi la funzione e' relativamente semplice secondo il datasheet, quando DATA va basso, bisogna inviare 24 impulsi di clock, larghi minimo 50uS (non c'e' un massimo, quindi il classico delayMicroseconds (4) di Arduino dovrebbe essere ok), e ad ogni ritorno del clock a zero, leggere il corrispondente bit, alla fine si manda un venticinquesimo impulso di clock per resettare DATA alto di nuovo ... inoltre ho deciso di usare un buffer circolare (per ora da 5 elementi) e fare una media fra tutti i valori contenuti nel buffer, per filtrare un po (avro' un ritardo massimo di aggiornamento di 0.5 secondi in questo modo, ma credo sia accettabile per avere un'aggiornamento del display ogni 100mS) ... credo cosi possa funzionare (ovviamente se non ho fatto qualche casino interpretando male gli esempi dei testi):

... 
// variabili globali per non dover passare valori qua e la con i return
// dichiaro locali solo quelle che servono solo all'interno della funzione
volatile int32_t bufADC[5] = {0}; // buffer circolare
volatile uint8_t indBuf = 0; // indice buffer
volatile int32_t mediaValori = 0; // media delle letture
...

// PIN_PD6 e' il clock (uscita), PIN_PD5  e' il data (ingresso)

void leggiADC() {
  int32_t valoreLetto = 0; // per immagazzinare il valore dell'ADC
  for (uint8_t i = 0; i < 25; i++) { // 25 impulsi di clock, ma i dati vengono letti solo per i primi 24
    digitalWrite(PIN_PD6, HIGH);       // mando alto il clock
    delayMicroseconds(4);              // aspetto 4ÎĽs (risoluzione minima di delayMicroseconds)
    digitalWrite(PIN_PD6, LOW);        // mando basso il clock
    delayMicroseconds(4);              // aspetto 4ÎĽs

    if (i < 24) {                      // per leggere solo dati validi
      valoreLetto = (valoreLetto << 1) | digitalRead(PIN_PD5);  // leggo ed aggiungo bit per bit a valoreLetto
    }
  }

  if (valoreLetto & 0x800000) {             // gestione valori negativi
    valoreLetto |= 0xFF000000;              // !!! DA CONTROLLARE !!!
  }
  
  bufADC[indBuf] = valoreLetto; // aggiorna buffer
  indBuf = (indBuf + 1) % 5;

  int64_t somma = 0; // fa la media del contenuto del buffer
  for (uint8_t i = 0; i < 5; i++) {
    somma += bufADC[i];
  }
  mediaValori = somma / 5;  // valore da usare altrove per i calcoli
}

NOTA: qualcuno si e’ divertito a sputtanare il funzionamento del tag “code”, che se non gli si lasciano almeno due righe prima e dopo converte in code l’intero messaggio, oppure spezzetta il codice ogni volta che trova una riga vuota ? … complimenti … :roll_eyes:

Secondo voi e' abbastanza corretta, oppure ho fatto cavolate ? ... nota: la gestione del segno l'ho presa da un'esempio in rete che parlava di uscita in complemento a 2, non so se sia corretta cosi.

Per quanto riguarda invece le funzioni di formattazione delle stringhe, e dei mal di testa che mi fanno venire ... Ancora non le posto perche' sono arrivato solo a meta', e neppure so se sia corretta questa meta', dato che la funzione dovra' fare parecchie cose in base al modo di lettura, vedremo piu avanti.

Di sicuro postero' tutto il programma, inclusi files Eagle e gerber, se riusciro' a tirarci fuori qualcosa di funzionante, cosi se qualcuno vorra' farsene uno, potra' farlo ... ma lasciatemi prima farlo funzionare ... per ora posto comunque lo schema elettrico, cosi per farvi divertire (mi raccomando, se iniziate a tirare pomodori, che siano maturi al punto giusto, ma non marci ne acerbi, cosi me li mangio :face_with_tongue: )

Piu avanti spiego anche il resto, che senno’ il post diventa chilometrico.

EDIT: Dimenticavo lo schema

complimenti

Calma con i complimenti, per ora ho realizzato solo schema e stampato, e quella era la parte facile :grin:

Seconda parte del papiro ... descrizione del funzionamento (o almeno di come vorrei che funzionasse) e qualche specifica tecnica.

Ho scelto di usare un display 16x2 solo perche' ne ho in casa una decina, e perche' cosi posso far stare tutto dentro un contenitore grosso piu o meno come un tester, il modello HP-9VB della PAC-TEC (anche qui, perche' li avevo gia in casa, sono abbastanza robusti, ed hanno un portabatteria per una 9V (nella versione definitiva probabilmente tagliero' via il portabatteria all'interno, incollero' lo sportello, e ci mettero' dentro due celle litio in serie con un loro circuito di protezione e di ricarica, ma con un'autonomia minore si puo usare anche la semplice pila da 9V, se non interessa avere uno strumento ricaricabile).

Lo strumento come detto prevede tre modalita' di misura, la prima (che e' anche quella di default ad ogni accensione) piccole correnti, mA, uA, nA (con una Rsense il piu bassa possibile, ho scelto 100 ohm, altrimenti non si possono misurare correnti molto basse, ma e' sempre meglio dei megaohm di molti degli strumenti in circolazione, suppongo ) , la seconda, piccole resistenze (usando una misura a 4 terminali ed un generatore di corrente interno, che puo dare 10 o 100 mA secondo la scala di misura), e la terza piccole tensioni (con un'impedenza di ingresso intorno ai 500megaohm, abbastanza alta da non influire troppo sulla maggior parte dei circuiti da testare.

All'accensione lo strumento, dopo il setup, parte sempre con la misura di corrente (Rsense da 100 ohm fra i terminali di misura, PGA impostato a 2) ed in questa modalita' dovrebbe essere in grado di misurare da un massimo di +/- 12.5mA ad un minimo di circa +/- 6nA, mentre commutando sulla seconda portata, "in teoria" dovrebbe poter misurare da un massimo di +/-195uA ad un minimo di circa +/- 1pA ... dico "in teoria", perche' a questi livelli perfino il "rumore termico" prodotto dai componenti passivi potrebbe falsare le misure, quindi non so se sia opportuno scendere cosi in basso ... potrei decidere di abbassare la Rsense a 10ohm, questo mi ridurrebbe le minime correnti misurabili "teoriche" a circa +/- 60nA in un caso e circa +/- 10pA nell'altro, alzando i fondoscala a +/- 125mA e +/- 1.95mA ... questo pero' potro' testarlo solo una volta che avro' lo stampato e riusciro' ad assemblare un prototipo, ed ovviamente fare dei test per vedere quanto il rumore termico ed i disturbi influiranno.

Sono previsti tre pulsanti, MODO (per cambiare ciclicamente fra le tre modalita' di lettura), SCALA (per cambiare l'amplificazione del PGA, e per la sola misura di resistenza, anche la corrente fra 10 o 100 mA), e OK ... siccome OK serve solo a confermare che si e' scollegato lo strumento prima di cambiare modo, perche' ad ogni pressione del pulsante MODO, visualizza un promemoria che chiede se si e' scollegato lo strumento, dato che e' meglio che non sia connesso, e poi aspetta continuando a fare bip finche' non si da conferma premendo OK, o si spegne (o si lancia dalla finestra infastiditi dai bip :grin: ), penso che gli daro' anche una seconda funzione, lanciare funzioni di calibrazione per il tipo di misura corrente quando lo si tiene premuto piu di 2 secondi, ma a quello devo ancora arrivarci.

Per ora mi fermo qui, se avete critiche, sparate pure (a salve, mi raccomando :grin: )

Ho tirato insieme un’altra funzione,per gestire i tre pulsanti e rilevare se P3 e’ premuto piu o meno di 2 secondi ma solo durante le letture, ci vedete errori ?

...
bool inPromem = false;  // per gestire P3 quando e' in funzione promemoria()
...

void leggiPuls() { // legge e gestisce i tre pulsanti
  static uint32_t tempoP3 = 0; // per gestire presione lunga P3
  static bool P3Prem = false; //idem

  if (digitalRead(PIN_PB0) == LOW) { // P1 premuto
    tone(BUZZER_PIN, 500, 20);
    while (digitalRead(PIN_PB0) == LOW);  // attende rilascio
	inPromem = true; // flag promemoria
    promemoria();  // chiama funzione promemoria()
  }

  if (digitalRead(PIN_PB1) == LOW) { P2 premuto
    tone(BUZZER_PIN, 500, 20);
    while (digitalRead(PIN_PB1) == LOW);  // attende rilascio
    cambiaScala();  // cambia scale, PGA, corrente, secondo la misura in corso
  }

  if (digitalRead(PIN_PB2) == LOW) { // P3 premuto, gestione incasinata
    if (!P3Prem) {  // se non era ancora premuto
      tempoP3 = millis();  // registra tempo inizio pressione
      P3Prem = true; // flag P3 premuto
    }

    if (inPromem) { // se sono dentro promemoria()
      while (digitalRead(PIN_PB2) == LOW);  // attende rilascio
      inPromem = false;
	  P3Prem = false;
      cambiaModo();  // Cambia modalitĂ 
    }
  } 
  else { // se NON sono dentro promemoria()
    while (digitalRead(PIN_PB2) == LOW);  // attende rilascio
    P3Prem = false;
    if (millis() - tempoP3 > 2000) { // P3 e' rimasto premuto piu di 2 secondi
      calibrazioni(); // chiama funzione calibrazioni
    }
	else { beepErr(); } // se P3 NON e' rimasto premuto piu di 2 secondi
    }
  }
}

C’e’ da considerare che P3 ha comportamenti diversi rispetto al momento in cui viene premuto … se lo si preme brevemente in lettura, dovrebbe fare solo i beep di errore, se lo si preme per piu di 2 secondi in lettura, al rilascio chiama la funzione di calibrazione, se lo si preme per qualsiasi durata di tempo mentre si e’ dentro la funzione promemoria, si limita a sbloccarla per proseguire.

Devo ancora trovare un sistema per ignorare i pulsanti che vengono premuti dove non devono fare nulla (e fargli fare solo i beep di errore), ma ci sto arrivando, pian piano … qualche altra flag ed un’altro po di incasinamento, e poi dovrebbero andare (forsepuodarsisperiamo :grin: ).

Ora non posso leggere tutto attentamente, ma se devi usare una logica di comando con l'enter, anziché pulsanti con funzioni dirette, non ti conviene usare un encoder?

Avevo pensato in origine ad un’encoder, ma ogni tasto ha solo quelle funzioni, e devono funzionare solo in determinate condizioni ed essere ignorate nelle altre, ho considerato che tre pulsanti erano la soluzione piu semplice.

Poi, hanno funzioni dirette, anche l’OK non e’ un “enter”.

EDIT: Mi spiego meglio:

P1, MODO, cambia fra le tre modalita' di lettura, ciclicamente, ma ad ogni pressione deve prima richiamare una funzione promemoria(), che visualizza un messaggio e poi enta in un while (bloccante, e' intenzionale) finche' P3 non viene premuto per confermare che lo strumento e' scollegato, a quel punto chiama una seconda funzione cambiaModo(), che pilota i vari pin in base al nuovo modo, quindi prosegue ... ma se viene premuto mentre si e' nella funzione calibrazione(), deve fare solo i beep di errore e nient'altro.

P2, SCALA, deve funzioare anche lui solo mentre si e' in fase di lettura, richiamando la funzione che cambia le scale in base alla lettura che si sta facendo, ma se viene premuto in altri punti (ad esempio mentre fai la calibrzione o mentre sei in promemoria, deve fare solo i beep di errore e nient'altro.

P3, OK/CAL, e' un po piu complesso ma neanche troppo, se lo premi brevemente mentre leggi, beep di errore, se lo premi piu di due secondi mentre leggi, lancia calibrazione (per il tipo di misura che stavi facendo), se lo premi dentro promemoria conferma di proseguire (indifferentemente dal tempo per cui lo tieni premuto) ...

La funzione che ho postato e' uno "scheletro" che dovro' completare con le esclusioni mancanti (P1 premuto in calibrazione, P2 premuto in calibrzione o promemoria), per ora sono arrivato qui, devo ancora aggiungere qualche flag e qualche controllo multiplo a quegli if per gestire tutto ... con un'encoder mi sarebbe diventato tutto piu incasinato.

Alla fine, la versione “corretta” (spero) della funzione che controlla i pulsanti, dovrebbe essere cosi (se non ho fatto errori):

bool inPromem = false;  // per gestire pulsanti errati in promemoria
bool inCalib = false; // per gestire pulsanti errati in calibrazione

void leggiPuls() { // legge e gestisce i tre pulsanti
  static uint32_t tempoP3 = 0; // per gestire presione lunga P3
  static bool P3Prem = false; // idem

  if ((digitalRead(PIN_PB0) == LOW) && inCalib) { // P1 premuto in calibrazione (errore)
    beepErr();
  }
  else if (digitalRead(PIN_PB0) == LOW) { // P1 premuto NON in calibrazione
    while (digitalRead(PIN_PB0) == LOW);  // attende rilascio
    tone(PIN_PE1, 500, 20);
	inPromem = true; // flag promemoria
    promemoria();  // chiama funzione promemoria()
  }

  if ((digitalRead(PIN_PB1) == LOW) && inCalib) || ((digitalRead(PIN_PB1) == LOW) && inPromem) { // errori
    beepErr();
  }
  else if (digitalRead(PIN_PB1) == LOW) {
    while (digitalRead(PIN_PB1) == LOW);  // attende rilascio
	tone(PIN_PE1, 500, 20);
    cambiaScala();  // cambia scale, PGA, corrente, secondo la misura in corso
  }

  if (digitalRead(PIN_PB2) == LOW) { // P3 premuto, gestione incasinata
    if (!P3Prem) {  // se non era ancora premuto
      tempoP3 = millis();  // registra tempo inizio pressione
      P3Prem = true; // flag P3 premuto
    }

    if (inPromem) { // se sono dentro promemoria()
      while (digitalRead(PIN_PB2) == LOW);  // attende rilascio
      inPromem = false;
	  P3Prem = false;
      cambiaModo();  // Cambia modalitĂ 
    }
  } 
  else { // se NON sono dentro promemoria()
    while (digitalRead(PIN_PB2) == LOW);  // attende rilascio
    P3Prem = false;
    if (millis() - tempoP3 > 2000) { // P3 e' rimasto premuto piu di 2 secondi
      calibrazioni(); // chiama funzione calibrazioni
    }
	else { beepErr(); } // se P3 NON e' rimasto premuto piu di 2 secondi
    }
  }
}

Le bool inCalib e inPromem vengono portate alte all’inizio delle relative funzioni, e riportate bassa subito prima di uscirne (o e’ meglio portarle alte prima di chiamare le funzioni ? )

Come fanno a funzionare quegli if/else if?…

Forse i due digitalRead sono ridondanti?

Mi sono incasinato facendo copia-incolla da notepad++, scusa :flushed_face:

Piu che altro, mi e’ appena venuta in mente una cosa … se sono all’interno di una funzione, la funzione leggiPuls() non viene piu richiamata finche sono all’interno di quella funzione, essendo chiamata dal loop … e’ giusto ?

Se e’ cosi, la cosa si semplifica parecchio, devo solo evitare di leggere da dentro le funzioni i pulsanti che non vanno premuti in quelle funzioni, quindi la gestione diventa molto piu semplice, e risparmio sia memoria di programma che tempo di esecuzione.

Dovrebbe funzionare in questo modo:

void leggiPuls() { // legge e gestisce i tre pulsanti
  static uint32_t tempoP3 = 0; // per gestire presione lunga P3
  static bool P3Prem = false; // idem

  if (digitalRead(PIN_PB0) == LOW) { // P1 premuto
    while (digitalRead(PIN_PB0) == LOW);  // attende rilascio
    tone(PIN_PE1, 500, 20);
	promemoria();  // chiama funzione promemoria()
  }

  else if (digitalRead(PIN_PB1) == LOW) { // P1 premuto
    while (digitalRead(PIN_PB1) == LOW);  // attende rilascio
	tone(PIN_PE1, 500, 20);
    cambiaScala();  // cambia scale, PGA, corrente, secondo la misura in corso
  }

  else if (digitalRead(PIN_PB2) == LOW) { // P3 premuto, gestione incasinata
    tempoP3 = millis();  // registra tempo inizio pressione
    while (digitalRead(PIN_PB2) == LOW);  // attende rilascio
    if (millis() - tempoP3 > 2000) { // P3 e' rimasto premuto piu di 2 secondi
      calibrazioni(); // chiama funzione calibrazioni
    }
	else { beepErr(); } // se P3 NON e' rimasto premuto piu di 2 secondi
  }
}

Questa funzione verra’ chiamata solo nel loop, da dentro altre funzioni controllero’ semplicemente se i pulsanti necessari vengono premuti con delle normali digitalRead, ignorando gli altri.

Ad esempio, la funzione promemoria() diventerebbe cosi (senza bisogno di gestione esterna della pressione di P3, che leggo da dentro la funzione)

void promemoria() { // per ricordare ai distratti di scollegare lo strumento fra i cambi di modo
      
  lcd.setCursor(0, 0);
  lcd.print(" HAI SCOLLEGATO ");
  lcd.setCursor(0, 1);
  lcd.print(" LO STRUMENTO ? ");

  while (digitalRead(PIN_PB2) != LOW) { // rimane qui dentro finche' non si preme OK
    if (millis() - 1500 >= beepInterv) {
      for (uint8_t i = 0; i < 3; i++) { // tre beep ad 1KHz da 20ms, ogni secondo e mezzo
        tone(PIN_PE1, 1000, 20);
        delay(20);
      }
      beepInterv = millis();
    }
  }
  aggiornaModi(); // Quando OK viene premuto
}

E mi si semplificano anche un paio di altre. … E’ corretto ?

Allora, avevo un po di tempo ed ho scritto qualche altro pezzetto, per il momento sono arrivato solo qui

strumentinoinfernale.ino (7,8 KB)

Lo allego cosi perche’ sta diventando un po troppo lungo per i copia/incolla, ma se lo volete come codice, basta dirlo.

Compilare, compila senza errori (il che per me’ e’ gia tanto), ma non e’ completo, mancano alcune funzioni (che sto ancora cercando di finire) … se qualche anima pia vuole darci un’occhiata per vedere se trova errori, e’ la benvanuta (il fatto che compili non vuol dire che io non abbia fatto cavolate)

Considerate che e’ fatto per il 328PB standalone con il minicore, per il 328 normale o arduino ovviamente da errori.

Se premo P1 o P2 fa il bip di errore?

Ma come fa se è dentro il while?

Lo ridico, magari non avessi colto il messaggio: Le logiche bloccanti sono strade che non spuntano.

Ciao.

No, ho deciso di rinunciare ai beep di errore (che erano una mia aggiunta superflua), mi limito a non leggere i pulsanti che non dovrebbero essere premuti da dentro le relative funzioni nelle quali non dovrebbero essere premuti.

Il while bloccante in questo caso e’ intenzionale, dopo aver visualizzato il messaggio di promemoria, io voglio che rimanga bloccato li dentro finche’ non si conferma che lo strumento e’ scollegato, in questo caso gestisco i tempi con millis per ripetere un beep ogni secondo e mezzo finche’ rimane li dentro, anche quello come promemoria, e non voglio che faccia altro finche’ non premi OK (per quello dicevo che e’ intenzionale)

Negli altri pochi casi in cui li uso, sono per aspettare che rilasci i pulsanti prima di fare le azioni, ed anche li non danno fastidio, perche’ finche’ non hai rilasciato il pulsante che avevi premuto, non deve fare nulla … anzi, in quei casi mi aiutano ad evitare letture di due pulsanti chiusi a GND insieme (che sarebbe sempre un’errore), perche’ in quel modo finche’ non esco dal rispettivo while, il programma non puo leggere un secondo pulsante (mentre se l’avessi fatto a flag ed if/else, sarebbe venuto piu incasinato e lungo)

Tendo pure io ad evitare di usare funzioni bloccanti, ma dove invece mi avvantaggiano, le uso senza problemi.

Io non capisco perché non usate una libreria per la gestione dei pulsanti, non solo tu. Hai tanta flash, e non trovo alcun buon motivo per non usare la JC_Button.

Inoltre la macchina a stati ti da la flessibilitĂ  che serve per non rinunciare ai bip, bop ecc. Comunque in questo caso comprendo la difficoltĂ  di ragionare a stati.

Questa cosa la fai tranquillamente con lo switch case, nel case specifico ci entri alla pressione di P1 e da questo case esci solo alla pressione di P3.

Ciao.

Perche’ con tre soli pulsanti, dei quali uno da gestire in modo doppio, mi sembrava un po esagerato … un paio di if e via, a me’ sembrano piu semplici.

Se volessi anche i beep di errore, mi basterebbe leggere tutti i pulsanti anche dentro le librerie dove non servono, ad esempio cosi:

void promemoria() { // per ricordare ai distratti di scollegare lo strumento fra i cambi di modo
  uint32_t beepInterv = millis();
  lcd.setCursor(0, 0);
  lcd.print(" HAI SCOLLEGATO ");
  lcd.setCursor(0, 1);
  lcd.print(" LO STRUMENTO ? ");

  while (digitalRead(PIN_PB2) != LOW) { // rimane qui dentro finche' non si preme OK

    if (digitalRead(PIN_PB0) == LOW) { beepErr(); } // beep di errore se si premono pulsanti diversi da P3 mentre si aspetta P3
    if (digitalRead(PIN_PB1) == LOW) { beepErr(); }

    if (millis() - 1500 >= beepInterv) {
      for (uint8_t i = 0; i < 3; i++) { // tre beep ad 1KHz da 20ms, ogni secondo e mezzo
        tone(PIN_PE1, 1000, 20);
        delay(20);
      }
      beepInterv = millis();
    }
  }
  cambiaModo(); // cambia la modalita' di misura ciclicamente
  aggiornaModi(); // aggiorna prima riga display
}

Magari alla fine, dopo aver completato tutte le sezioni, se vedo che non occupa troppa memoria glie li aggiungo pure … ma ne manca ancora parecchia di roba.

Sto litigando con la gestione del bitshift per togliere gli ultimi due bit LSB (per ridurre il rumore) anche dai numeri negativi … per i positivi, l’ho capita perfino io ( :face_with_tongue: ), faccio

mediaValori = mediaValori >> 2;

e dovrei ritrovarmi con un valore che da 24 bit e’ passato a 22 … ma per i negativi ? … in un’esempio in rete ho trovato una cosa simile (convertita con le mie variabili)

if (mediaValori < 0) { // se il numero e' negativo ?
    mediaValori = -((-mediaValori) >> 2);
} else {
    mediaValori = mediaValori >> 2;
}

… non capisco molto il senso di negare il risultato di un’operazione su un numero negativo che viene negato di nuovo (a meno che non si tratti di trasformare un negativo in positivo, eseguire l’operazione, e poi trasformarlo di nuovo in negativo, tutto nella stessa riga, ma non ne sono sicuro, non mi e’ mai capitato di farlo fin’ora … e’ davvero cosi macchinoso fare operazioni bitshift su numeri negativi ? ) :roll_eyes: … secondo voi e’ corretto scritto cosi ?

Non ho proprio idea di cosa rappresentano questi 24 bit, poi vedo il datasheet del chip che stai usando.

In genere appena fai lo shift a destra anche di una solo posizione il bit piĂą significativo (quello a sinistra) se era 1 (numero negativo) diventa zero (numero positivo), per cui si con gli interi con segno non solo tronca il bit meno significativi, ma trasforma un numero negativo in positivo.

Ciao.

Mi sembra corretto… Scorre a destra un valore positivo. Se è negativo, lo trasforma in positivo, lo scorre e poi lo ritrasforma in positivo.

Prova anche così:

if (mediaValori>0) mediaValori=mediaValori>>2;
else mediaValori=-(abs(mediaValori)>>2); 

(544 + 13 byte).

oppure:

bool neg;

loop:
neg = (mediaValori<0);
mediaValori = (abs(mediaValori))>>2;
if (neg) mediaValori=-mediaValori;

(542 + 13 byte).

Se al numero con segno viene applicata un operazione matematica piuttosto che uno scorrimento basta dividere per 4 il numero

Dovrebbe funzionare

Sì, certo, ma la divisione è un'operazione complessa, lenta. Non so se le divisioni per potenze di due vengono ottimizzate dal compilatore, secondo il livello di ottimizzazione impostato e quando possibile, con lo scorrimento a destra.

Funziona?…

if (mediaValori<0) mediaValori|=0b11000000000000000000000000; mediaValori>>=2;

Da Android non riesco ad andare a capo nel codice!