Genauigkeit der Spannungsmessung

Hallo zusammen,
bei einem anderen größeren Projekt fiel mir auf, dass die Spannungsmessung recht ungenau ist. Zum Vergleich habe ich identische Hardware (Nano, Spannungs-Sensor) verwendet und mit zwei Sketches verglichen.
Version A ergibt mit der internen Referenz von 1,1 Volt eine hohe Genauigkeit, dass ich auf den geplanten Einsatz des LM4040 verzichten kann.
Version B hat eine Abweichung von 0,08 Volt; alles bezogen auf die Messung mit einem Multimeter. In meinem größeren Sketch ist Version B eingebaut und ich beabsichtige, alles auf Version A umzubauen. Kann das bitte jemand checken, ob die gleichen Werte herauskommen?
Vielen Dank.
Hier die beiden Sketches:
Version A:

/*
  Arduino DC Voltage Demo 1
  dc-voltage-demo.ino
  Use Arduino A/D converter to measure voltage
  Use external voltage divider with 30k & 7.5k resistors
  Results displayed on Serial Monitor

  DroneBot Workshop 2021
  https://dronebotworkshop.com/dc-volt-current/
*/

// Define analog input
#define ANALOG_IN_PIN A0

// Floats for ADC voltage & Input voltage
float adc_voltage = 0.0;
float in_voltage = 0.0;

// Floats for resistor values in divider (in ohms)
float R1 = 30000.0;
float R2 = 7500.0;

// Float for Reference Voltage
//float ref_voltage = 5.0;
float ref_voltage = 1.1;

// Integer for ADC value
int adc_value = 0;

void setup() {
  // Setup Serial Monitor
  Serial.begin(9600);
  Serial.println("DC Voltage Test");
  analogReference(INTERNAL);
}

void loop() {
  // Read the Analog Input
  adc_value = analogRead(ANALOG_IN_PIN);

  // Determine voltage at ADC input
  adc_voltage  = (adc_value * ref_voltage) / 1024.0;

  // Calculate voltage at divider input
  in_voltage = adc_voltage / (R2 / (R1 + R2));

  // Print results to Serial Monitor to 2 decimal places
  Serial.print("Input Voltage = ");
  Serial.println(in_voltage, 2);

  // Short delay
  delay(2000);
}

Version B:

// Pin Spannungsmesser
const byte solarPin = A0;

const unsigned int REF_VOLTAGE = 11;
const unsigned int PIN_STEPS = 1024.0;
const unsigned long multiplikator = 1000;

unsigned int vout = 0, vin = 0;
unsigned int solarV = 0; // Solarzelle

// Spannungsteiler
unsigned int R1 = 30000; // resistance R1 (=  30 KOhm)
unsigned int R2 =  7500; // resistance R2 (= 7,5 KOhm)

// Pausen
unsigned long startTime;              // Startzeit

void setup() {
  Serial.begin(9600);
  Serial.println("Start");
  pinMode(solarPin, INPUT);
  analogReference(INTERNAL);

}

void loop() {
  spannungsMessung();     //Ausgabe Monitor
}

float voltMess(const byte messstelle)
{
  uint32_t vout = 0;
  switch (messstelle)
  {
    case 0:
      vout = analogRead(solarPin) * REF_VOLTAGE * multiplikator / PIN_STEPS;
      //vout = 1023 * REF_VOLTAGE * multiplikator / PIN_STEPS;
      vout = vout / (R2 * multiplikator / (R1 + R2));
      break;
  }
  return vout / 10.0;
}

void spannungsMessung() //Ausgabe Monitor
{
  const unsigned long period1 = 2000;  // Pausenlänge Spannungsmesser
  // Spannungsmesser: Ausgabe Monitor
  if (millis() - startTime >= period1) //Pause1 abgelaufen?
  {
    startTime += period1;
    // A0
    Serial.print(F("U Solar =  "));
    Serial.print(voltMess(0), 2);
    Serial.println(F(" V"));
  }
}

Messergebnisse:
Multimeter: 4,68 Volt
Version A:
Monitor: 5,00 Volt
Mit int. Referenz: 4,68 Volt

Version B:
Monitor: 5,00 Volt
Mit int. Referenz: 4,60 Volt !

Hast Du die Widerstandswerte ausgemessen? Die sind selten so genau, wie in Deinem Sketch angegeben.
Hast Du die 1,1 V interne Referenz ausgemessen? Die unterliegt auch Fertigungstoleranzen.

Der Sinn der folgenden Zeile erschließt sich mir nicht. Könnte es sein, dass da Wert und Kommentar nicht zusammen passen?

const unsigned int REF_VOLTAGE = 11;      // entspricht 5,0 V

Gruß Tommy

Bis zu 10% Abweichung ist normal!

Ich rate zu einer Kalibrierung.
Also weder A noch B, sondern C
Das EEPROM bietet sich zum speichern an.

Geradengleichung, lineare Interpolation.
Hört sich schlimmer an, als es ist.

Die Werte weichen geringfügig ab (7,52K, 29,94K). Für beide Sketches wird die gleiche Hardware verwendet, so dass es zu vernachlässigen ist.

Die Referenzspannung weicht erst ab der zweiten Kommastelle ab und durch den Vergleich (Hardware s. o.) ist das nicht relevant.

Habe ich entfernt. Der Kommentar trifft bei Verwendung der Referenz nicht (mehr) zu.

Im Grunde sollte bei A und B das gleiche Ergebnis rauskommen; tut es aber nicht. Daraus schließe ich, dass Version A genauer ist. Warum, kann ich nicht beurteilen.

Wenn Du vom float weg willst, musst Du alles in integer/long umschreiben.
Ich hatte das doch gerade erst vor ein paar Tagen....

Ob das evtl. eine falsche Schlußfolgerung ist, kann ich nicht beurteilen.
Nur weil Dein Multimeter die selbe Rechengrundlage benutzt heisst das noch lange nicht, das Version b falsch ist.

Du musst nur eines berücksichtigen:
Wenn Du mit ganzzahlen bis vor die Ausgabe rechen willst, muss der Wertebereich das auch hergeben.
Ein float dazwischen oder eine verkürzte Zahl haut Dir alles weg.

Habe eben nochmal die Referenzspannung gemessen: 1,113 Volt.
Da in beiden Versionen die gleiche Spannung verwendet wird, kann es daran nicht liegen. Beim Einsatz des LM4040 gab es die gleichen Ergebnisse, weshalb ich für diesen Zweck darauf verzichten will.

Ich verstehe deine Gedanken, aber die haben wenig mit meiner Ansage zu tun.

Weder die Widerstandswerte, noch die absolute Referenzspannung würde ich in irgendeine der Berechnungen einfließen lassen.
Alles flüssiger als Wasser.... Überflüssig.

Aber ok, dem Menschen, sein Himmelreich, sind seine Gedanken.

Ich weiß. Nur beim weiteren Programmieren fiel mir der Unterschied auf, den ich mir nicht erklären kann.

Ich weiß es eben auch nicht. Bei meinen Tests lade ich Version A, notiere die Ergebnisse und lade direkt danach Version B. Da sollte doch das gleiche Ergebnis rauskommen. Kannst du bitte die beiden Sketches mal laden und vergleichen?

Ok, das war das Ding mit dem OLED und der Berechnung da drin. Es gab nach #66 irgendwie kein weiterkommen.

Hättest oben gleich dazu schreiben können; der interessierte Leser hätte dann

nachlesen können auf welchen Stand Du zurückgreifst.

Das Prinzip dort ist noch immer das gleiche.
Wenn Du mit Ganzzahlen rechnen willst, dann alles.
Für Divisionen brauchst Du genügend Stellen um zu vermeiden, das abgeschnitten wird.

Was erwartest Du denn für eine Genauigkeit?
Der ADC hat 10 bit Auflösung.
In welchem Bereich findet sich Deine Eingansspannung.

Das hängt zwar mit dem anderen Projekt zusammen, aber mir ging es um das Prinzip. Damit sich keiner in den anderen Thread einlesen muss, habe ich mir die Mühe gemacht und zwei identische Version, abgesehen von der Berechnungsmethode, erstellt. Damit kann jeder ohne großen Aufwand das Problem nachvollziehen.

Für die Genauigkeit reicht mir eine Stelle nach dem Komma. Wenn es also 4,68 Volt sind, werden (bei einer Kommastelle) 4,7 Volt angezeigt. Bei Version B sind es 4,6 Volt.
Der benötigte Spannungsbereich liegt zwischen 10,5 und 14,8 Volt. Um Schäden an den Accus zu vermeiden, will ich die Spannung recht genau wissen, um z. B. eine Überspannung zu vermeiden. Das würde den verschlossenen Blei-Accu zum Kochen bringen.
Auf die Ungenauigkeit des Ergebnisses bin ich beim Test mit dem LM4040 gestoßen und konnte es mir nicht erklären. Deshalb meine Fragen im Forum.

P.S.:
Wenn die Hardware im Schaltschrank eingebaut ist, kann ich mal ein Bild schicken. Ich bin schon seit Anfang Dezember 2021 an der Entwicklung, wobei sich auch die Hardware aufgrund neuer Erkenntnisse mehrfach geändert hat.

Wenn alle Abweichungen für Dich keine Relevanz haben, warum beschäftigst Du Dich dann überhaupt mit Genauigkeit?

Gruß Tommy

Auf eine Stelle nach dem Komma hätte ich es schon gerne (s. o.).

Ja.
Sag ich doch.
Abgeschnitten und nicht gerundet.

Dann liegt es wohl an der Methodik, dass unterschiedliche Werte angezeigt werden. Aus dem Bauch heraus neige ich zu Version A, auch wenn das wieder Umbauarbeiten bedeutet.

Nein, am Verständnis.
Warum machst Du z.B. das:

in B?

Könnte noch von dir sein. Habe ich aus meinem Projekt herauskopiert, aber auf eine Messung reduziert. Wenn ich es auskommentiere, wird keine Spannung mehr angezeigt.

Ach, das ist ja schon der Rückgabewert.
Dann musst die Nachkommastelle berechnen und nicht abschneiden.

Variante C:
digital: dort den per analogRead() gemessenen Wert eintragen
analog: hier den mit einem Multimeter gemessenen Wert eintragen

Der Messpunkt sollte nahe am Maximum liegen, je näher, desto genauer

Dann ist der Messvorgang kalibriert.

#include <Streaming.h> // die Lib findest du selber ;-)
Print &cout = Serial; // cout Emulation für "Arme"

constexpr unsigned digital  {966};   // ADC Value
constexpr float    analog   {14.22}; // gemessene Spannung
constexpr float    steigung {analog/digital};

void setup() 
{
  Serial.begin(9600);
  analogReference(INTERNAL);
  cout << F("Start: ") << F(__FILE__) << endl;
  cout << F("Steigung: ") << _FLOAT(steigung,6) << F(" Volt pro Digit") << endl;
}

void loop() 
{
  delay(2000);
  unsigned messwert {analogRead(A0)};
  cout << F("Digital: ") << messwert 
       << F("   Spannung: ") << _FLOAT(messwert*steigung,2) 
       << endl;
}

Dank der Toleranzen der internen Referenz und auch der Toleranzen der Widerstände, ist die Steigung auf jedem AVR leicht anders, drum macht es Sinn, diese im EEPROM abzulegen.

Was für Mathe:

      // Variante1:
      // Messtelle liefert MaxWert:
      // 1023 * 11 * 1000 / 1024 = 11253000 /1024
      vout = (analogRead(solarPin) * REF_VOLTAGE * multiplikator) / PIN_STEPS;
      // vout = 10989
      // vout = 10989 / (7500*1000 / (30000+7500))
      // vout = 10989 / (7500000 / 37500)
      // vout = 10989 / 200
      // vout = 54
      vout = vout / (R2 * multiplikator / (R1 + R2));
      break;
  }
  return vout / 10.0; // liefert 5,4


      // Variante2: (ACHTUNG DER REFENZWERTWERT IST JETZT 110!)
      // Messtelle liefert MaxWert: 
      // 1023 * 110 * 1000 / 1024 = 112530000 /1024
      vout = (analogRead(solarPin) * REF_VOLTAGE * multiplikator) / PIN_STEPS;
      // vout = 109892
      // vout = 109892 / (7500*1000 / (30000+7500))
      // vout = 109892 / (7500000 / 37500)
      // vout = 109892 / 200
      // vout = 549
      vout = vout / (R2 * multiplikator / (R1 + R2));
      break;
  }
  return vout / 10.0; // liefert 5,5
}

Na dann.
PS: Der Analogin liefert immernoch nur 1023....

Da blicke ich ehrlich gesagt nicht durch. Habe das Beispiel eingegeben und erhalte folgende Werte:

19:54:26.709 -> Steigung: 0.014716 Volt pro Digit
19:54:28.675 -> Digital: 799   Spannung: 11.76
19:54:30.694 -> Digital: 865   Spannung: 12.73
19:54:32.663 -> Digital: 863   Spannung: 12.70
19:54:34.689 -> Digital: 864   Spannung: 12.71
19:54:36.704 -> Digital: 864   Spannung: 12.71
19:54:38.672 -> Digital: 864   Spannung: 12.71

Was spricht gegen Version A? Damit wäre ich zufrieden.