Controllo livello batteria con risultati anomali

Sono alcuni giorni che non riesco a far funzionare questo codice o meglio funzionare funziona ma mi arrivano dei risultati completamente sballati e impossibili:

void BATTERY() {
  const int maxRetries = 5; // Massimo numero di tentativi per evitare loop infiniti
  int retryCount = 0;      // Contatore dei tentativi

  do {
    analogSetPinAttenuation(5, ADC_11db); // Imposta l'attenuazione per una gamma di tensione più alta
    const int numSamples = 100;          // Aumenta il numero di campioni per una lettura più stabile
    float minVoltage = 2.80;             // Valore minimo per 0%
    float maxVoltage = 4.20;             // Valore massimo per 100%

    // Chiamata a readVoltage() per ottenere una lettura media dei campioni
    float PIN5 = readVoltage(5, numSamples);

    // Calcola la tensione media (supponendo che PIN5 sia già la media dei campioni)
    voltageLevel = 3.3f * PIN5 * 2 / 4096.0f;

    batteryFraction = (voltageLevel - minVoltage) / (maxVoltage - minVoltage);
    if (batteryFraction < 0) batteryFraction = 0; // Assicura che la percentuale non sia negativa
    batteryFraction = constrain(batteryFraction, 0, 1); // Limita la percentuale tra 0 e 1 (0-100%)

    Serial.println((String) "Raw:" + PIN5 + " Voltage:" + voltageLevel + "V Percent: " + (batteryFraction * 100) + "%");
    delay(100); // Ridotto il delay per velocizzare i tentativi in caso di errore

    retryCount++; // Incrementa il contatore dei tentativi

  } while (voltageLevel > 4.5 && retryCount < maxRetries);

  // Se dopo i tentativi la tensione è ancora alta, potresti voler gestire questo caso
  if (voltageLevel > 4.5) {
    Serial.println("ATTENZIONE: Tensione ancora troppo alta dopo " + String(maxRetries) + " tentativi. Controllare il sensore!");
    logString += "ATTENZIONE: Tensione ancora troppo alta dopo " + String(maxRetries) + " tentativi. Controllare il sensore!";
  }
}

Ora preciso che ovviamente il divisore è 2, testato anche con multimetro.

Qualche idea?

Magari sapere che scheda usi, che batteria misuri e cosa ti esce in stampa aiuterebbe

Hai ragione perdonami.

La batteria è una batteria al litio con un range da 2.80 a 4.20v (il BMS stacca se scende sotto 2.70 quindi andrebbe a 0v ma per i miei scopi mi va bene considerare lo 0% leggermente più alto).

La scheda è una ESP32C6.

Nella stampa escono valori apparentemente randomici e completamente impossibili ad esempio a volte mi dice che la batteria raggiunge 4.7v ma è impossibile perché il BMS staccherebbe prima e andrebbe in protezione e comunque dubito che una batteria possa raggiungere 4.7v.
Altre volte mi dice che il voltaggio scende 3.1v che è un valore possibile ma mi sembra altamente improbabile specialmente perché avviene nell'arco di pochissimo tempo (20 minuti).

La batteria è stata testata a parte, scollegandola da tutto, e non ha presentato anomalie.

Ti faccio un esempio:

Nina, [13/06/2025 20:00]
Friday 13/06/2025 - 20:00:09
BOT @Saviolobot online
Livello batteria: 0.00%
Voltaggio: 2.371633

Nina, [13/06/2025 20:20]
Friday 13/06/2025 - 20:20:15
BOT @Saviolobot ha annaffiato
Livello batteria: 21.88%
Voltaggio: 3.106270
Differenza livello: 21.88%
Differenza voltaggio: 0.734637

questa è l'attivazione di ieri sera alle 20 ma è letteralmente impossibile che nel momento in cui l'ESP32 si è acceso il voltaggio fosse 2.37v e dopo che aveva alimentato la pompa di irrigazione quella stessa batteria che nel frattempo non ha alcun modo di ricaricarsi sia salita di voltaggio a 3.10v.

Con questa formula calcoli la tensione considerando:

  • 3,3 V di riferimento
  • 12 bit di risoluzione
  • una attenuazione di 0,5 (divisore 2)

Qualcosa tra queste è la colpevole :wink:
(sempre che non ci siano anche disturbi elettrici)

Concordo

Ma io guarderei anche la stabilità del riferimento

Non ho mai usato il C6, ma l'ADC degli ESP32 è particolarmente rognoso e molto soggetto a disturbi e va configurato a dovere.
Le API Arduino spesso non sono sufficienti per ottenere un buon risultato ed è meglio ricorrere alle istruzioni native ESP-IDF.

Se mi ricordo, domani ti posso girare lo sketch con cui avevo ottenuto risultati migliori, nel frattempo dai un'occhiata qui.

Potrebbe anche andare in overflow la funzione che fa lettura e media

Se readVoltage legge la tensione (e la variabile si chiama PIN5, che sembra il numero del pin...), perché poi dividi per 4096 e moltiplichi per 3,3?

PIN5 (l'ho chiamato così per semplicità e sì confermo è anche il pin collegato).

Da come l'ho capita io poi correggetemi se sbaglio:

PIN5 effettua 100 letture e ne fa la media. Però quando effettua la lettura essa non assume il valore del voltaggio direttamente ma viene letta come un numero tra 0 e 4095.

Quindi PIN5/4096.0f = il valore del voltaggio rispetto al riferimento.

Essendo il riferimento la tensione a 3.3v per ottenere il voltaggio reale devi poi moltiplicare per la tensione di riferimento e nel mio caso dovendo abbassare la tensione per non sovraccaricare la scheda moltiplicare per 2.

Quindi riassumento PIN5/4096f = valore di riferimento.

Valore di riferimento * 3.3f = Voltaggio reale dopo il divisore.

Voltaggio dopo divisore * 2 = voltaggio batteria

Almeno teoricamente dovrebbe essere così.


Oggi ho modificato il circuito perché mi è venuto il dubbio che possa essere la terra a non essere stabile in quanto non aveva un collegamento diretto e unico. Ho ipotizzato ci potessero essere dei disturbi. Vediamo se cambia qualcosa.

I 3,3v ho provato a fare varie simulazioni ma anche quando si accende la pompa che comunque è comandata da un relé optoisolato non ha variazioni significative (la massima varianza che sono riuscito a simulare è di 0,03v) e comunque la lettura della batteria avviene prima che si accenda la pompa e dopo che si è già spenta.

Solo tu sai, cosa fa "readVoltage"...
Che valori usi per voltage divider?
Ps. Potresti usare analogReadMilliVolts invece di analogRead.

Mi sono effettivamente accorto che c'è un errore in readVoltage anche se non dovrebbe aver inciso così tanto...

Allego la versione attuale di readVoltage:

float readVoltage(int pin, int numSamples) {
  float sum = 0;
  int validSamples = 0;
  const float referenceVoltage = 3.3; // Tensione di riferimento (es. 3.3V)
  const float adcResolution = 4095.0; // Per ADC a 12 bit (usa .0 per calcoli in virgola mobile)

  for (int i = 0; i < numSamples; i++) {
    int rawSample = analogRead(pin);

    // Converte il valore grezzo in tensione PRIMA del confronto
    float voltageSample = (rawSample / adcResolution) * referenceVoltage;

    if (voltageSample > 1.30) { // Ora il confronto è corretto (Volt vs Volt)
      sum += voltageSample;       
      validSamples++;
    }
    delay(10);
  }

  // La media sarà già un valore di tensione
  return validSamples > 0 ? sum / validSamples : 0.0;
}

e di conseguenza modifico battery:

void BATTERY() {
  const int maxRetries = 5; // Massimo numero di tentativi per evitare loop infiniti
  int retryCount = 0;      // Contatore dei tentativi
  float PIN5;

  do {
    // Reinizializza la tensione filtrata ad ogni tentativo di ripetizione
    // In questo modo, il filtro parte da zero con le nuove letture.
    // filteredVoltage = 0.0; // Potresti volerlo azzerare, ma il filtro esponenziale si adatterà comunque.
                           // Lo mantengo così per un adattamento più rapido in caso di spike temporanei.

    analogSetPinAttenuation(5, ADC_11db); // Imposta l'attenuazione per una gamma di tensione più alta
    const int numSamples = 100;          // Aumenta il numero di campioni per una lettura più stabile
    float minVoltage = 2.80;             // Valore minimo per 0%
    float maxVoltage = 4.20;             // Valore massimo per 100%

    // Chiamata a readVoltage() per ottenere una lettura media dei campioni
    PIN5 = readVoltage(5, numSamples);

    // Calcola il livello di batteria usando la tensione filtrata
    batteryFraction = (PIN5 - minVoltage) / (maxVoltage - minVoltage);
    if (batteryFraction < 0) batteryFraction = 0; // Assicura che la percentuale non sia negativa
    batteryFraction = constrain(batteryFraction, 0, 1); // Limita la percentuale tra 0 e 1 (0-100%)

    Serial.println((String) "Raw:" + PIN5 + " Voltage:" + PIN5 + "V Percent: " + (batteryFraction * 100) + "%");
    delay(100); // Ridotto il delay per velocizzare i tentativi in caso di errore

    retryCount++; // Incrementa il contatore dei tentativi

    // Condizione per ripetere il ciclo: se filteredVoltage è maggiore di 4.5V E non abbiamo esaurito i tentativi
  } while (PIN5 > 4.5 && retryCount < maxRetries);

  // Se dopo i tentativi la tensione è ancora alta, potresti voler gestire questo caso
  if (PIN5 > 4.5) {
    Serial.println("ATTENZIONE: Tensione ancora troppo alta dopo " + String(maxRetries) + " tentativi. Controllare il sensore!");
    logString += "ATTENZIONE: Tensione ancora troppo alta dopo " + String(maxRetries) + " tentativi. Controllare il sensore!";
  }
}

E...?

Rullo di tamburi
....

Il vincitore è....

Dovrai aspettare stasera dopo le 21 perché con le modifiche effettuate mi ha dato risultato 0v a tutto quindi o ieri rimontando ho collegato qualcosa male o per qualche motivo persiste un problema T_T

Potrebbe essere che abbia collegato qualcosa male... ieri c'era la bambina che mentre lo rimontavo non dava pace. Però prometto di aggiornarvi appena possibile.


AGGIORNAMENTO:

Allora tornato a casa ho fatto varie prove al banco usando un alimentatore quindi con un voltaggio estremamente controllato e verificabile:

Prima di tutto parliamo delle cose incomprensibili:

float voltageSample = 1.25 * (rawSample / adcResolution) * referenceVoltage;

Mi domanderete perché c'è quel 1.25 a moltiplicare? Ebbene non riesco a capirlo nemmeno io ma chiedendo a diversi amici tutti mi dicono che va inserito per forza anche se nessuno sa dirmi il perché ed effettivamente al banco con quel moltiplicatore ottengo risultati con un errore medio di circa 0,01v. Rimango estremamente confuso sul perché sia necessaria quella sorta di costante ma a quanto pare è necessaria.

Il secondo aspetto che ho notato è che è importantissimo che il GND sia identico sia per alimentazione che test. Basta che il GND abbia anche solo una minima ma veramente minima differenza che escono numeri a caso ma veramente completamente sballati.

Per darvi un'idea se uno il GND dell'alimentazione (sempre usando al banco un alimentatore controllato) senza collegare il GND dell'alimentatore da banco che invece simula la batteria appaiono risultati senza senso tipo:

0.00
3.60
0.10
2.70

e così via valori che almeno all'apparenza sembrerebbero randomici o quasi.

Quindi adesso andrò a riprovare usando la batteria e stasera alle 20.30 circa vi aggiorno se modificando la formula e cercando di usare il GND comune si riesce a ottenere un risultato possibile.

Sarebbe perfettamente comprensibile se nella catena di misurazione comparisse una attenuazione della tensione pari a 0.8

C'é?

No, come ho scritto sopra prima ho provato usando un alimentatore da banco quindi sono sicurissimo della tensione che arrivava al pin 5 e tra l'altro l'ho verificata anche con un multimetro a parte.

A quanto sono riuscito a trovare ma senza riscontri ufficiali quel 1.25 deriva da:

il profilo a 11db prevede un voltaggio in ingresso da 0 a 3.9v, essendo Vref interno al microcontrollore 3.3 ottieni che:

3.9/3.3=1,18

mettici poi che Vref non è così preciso a 3.3 e ottieni che 1.25 è una sorta di calibrazione.

Però ripeto non ho trovato documentazione che asserisse ufficialmente una cosa del genere ma solo altri utenti che ipotizzavano questa cosa.

Per forza! Se colleghi il positivo di una pila a una lampadina o a un tester ma non colleghi la massa, come può funzionare?...

Non mi sono spiegato bene, intendevo dire che se hai da una parte per esempio 5v (al pin adatto) e GND e poi dall'altra (ad esempio all'altro pin GND accanto al pin 3.3v) un GND che non è esattamente lo stesso ma un GND che viene da una qualsiasi altra fonte succede che produce risultati strani.

Per spiegarmi meglio data questa scheda se colleghi 5v in basso a destra e GND, e poi in alto a sinistra colleghi un GND che però non è lo stesso collegato in basso a destra la scheda funziona lo stesso perché sono comunque due GND ma quando deve misurare il voltaggio produce valori apparentemente casuali.

11 dB significa un rapporto tra tensioni pari a 3.548:

11 = 20 log10 (V1/V2)

V1/V2 = 10(11/20) = 3.548

Il fondoscala 3.9 è concorde con un riferimento di 1.1 V, infatti:

1.1 x 10 (11/20) = 3.9

Se moltiplichiamo il riferimento per 3 (quindi lo portiamo a 3.3 V) e contemporaneamente dividiamo per 3 il rapporto tra tensioni (portandolo da 3.548 a 1.1826), a parità di tensione di ingresso leggiamo lo stesso valore.

Vmis = ADC x (Vrif/Risol) x (1/Attenuaz) 
Vmis = ADC x (1.1/4096) x 3.548
Vmis = ADC x (3.3/4096) x 1.826

Considerando una tolleranza superiore al 5% della tensione di riferimento, il valore 1.25 appare perfettamente plausibile.

Di fatto tutta la parte destra dell'equazione si può trasformare in un valore di calibrazione ottenuto da una lettura precisa della tensione in ingresso e dal valore fornito dall' ADC:

cal = Vin / ADC

Questo valore compensa tutte le tolleranze (comprese quelle di eventuali resistenze se si usasse un partitore esterno invece dell'attenuazione interna).

A prescindere dai conti fatti per ottenere un livello in tensione "sensato" a partire dal valore adimensionale dell'ADC, il problema principale di @saviolo_t mi pare di capire che sia principalmente l'estrema variabilità delle misure.

Come c'è scritto chiaramente nel link che avevo messo qualche post fa:

The ESP32-C6 ADC can be sensitive to noise leading to large discrepancies in ADC readings. Depending on the usage scenario, you may need to connect a bypass capacitor (e.g. a 100 nF ceramic capacitor) to the ADC input pad in use, to minimize noise. Besides, multisampling may also be used to further mitigate the effects of noise.

Io ho avuto gli stessi problemi di instabilità nelle letture con l'ESP32-C3 (l'ADC integrato dovrebbe essere sostanzialmente lo stesso) ed ho fatto molta fatica ad ottenere risultati accettabili con le API Arduino (la classica analogRead() per intenderci).
Non so se nel frattempo hanno migliorato i sorgenti del core, ma l'esempio incluso nell'ambiente proprietario Espressif funziona molto bene e non ci vuole molto a convertirlo in uno sketch compilabile con Arduino IDE.