ATiny85 Betriebsspannung messen fehlerhaft. Warum?

Hallo zusammen.

Ich habe mich mit der Messung der Betiebsspannung mit Hilfe der internen Referenzspannung von einem ATTiny85 beschäftigt. Grundlage dafür ist dieser Forenbeitrag https://www.arduinoforum.de/arduino-Thread-Messen-der-eigenen-Betriebsspannung-mit-dem-Arduino

Mein Problem ist, dass die interne Referenzspannung eben gar nicht konstant ist. Wenn ich die Referenzspannung, wie in dem verlinken Artikel beschrieben, bei einer Betriebsspannung von 5V ermittle, bekomme ich einen Referenzwert von 986mV angezeigt.
Messe ich die Betriebsspannung bei 5V mit dem unten gezeigten Programm, passt das Ergebnis. Senke ich aber die Betriebsspannung ab, verfälscht sich das Ergebnis immer mehr.

Durch ausprobieren, habe ich einen Wert von 1086mV bei 3V Betriebsspannung ermittelt. Verwende ich diesen Wert als internen Referenzwert, so passt das Ergebnis perfekt bei 3V. Erhöhe ich die Betriebsspannung auf 5V so ermittelt das Programm einen Wert von 5532mV.

Ich habe eine Messreihe erstellt, indem ich die ermittelten Werte von drei Volt aufwärts (in 500mV Schritten) in eine Tabelle eingetragen habe. In ein Diagramm umgesetzt kann man schön sehen, wie die vom Programm ermittelten Werte mit steigender Betriebsspannung immer mehr von der Realität abweichen.

Nun stellt sich mir die Frage, mache ich etwas grundsätzliches falsch? Ist das schlicht ein Gain - Error vom ADC? Hat jemand eine Idee, was da verkehrt läuft?

Das Programm:

#include <Arduino.h>
#include <util/atomic.h>
#include <SendOnlySoftwareSerial.h>   //  https://github.com/nickgammon/SendOnlySoftwareSerial

SendOnlySoftwareSerial soSerial(PIN_PB3);   // TX = PB3

void initADC0() {
  // Read 1.1V reference as input with reference operating voltage vcc
  // Reference Vcc and analog input = internal reference 1.1V
  // Initialise ADC with REFS[2:0] is 0 = VCC as Ref,  MUX[3:0] 1100 = Vbg as Input,
  ADMUX = _BV(MUX3) | _BV(MUX2);
  ADCSRA = _BV(ADPS2) | _BV(ADPS1);   // Samplingrate = 125kHz with 8Mhz Corecycles
  ADCSRA |= _BV(ADEN);                // Enable,
  delay(5);                           // Wait until the reference has stabilized
  // After activating the ADC, a "dummy readout" is recommended.
  // In other words, a value is read and discarded to allow the ADC to "warm up"
  while ((ADCSRA & _BV(ADSC))) { ; }
  (void)ADCW;   // Discard dummy readout..
}

constexpr uint16_t INTERNAL_REF {1086};   // determined per IC
constexpr uint32_t INERNAL_REFxRESOLUTION {INTERNAL_REF * 1024UL};

uint16_t measurementVCC() {
  constexpr uint8_t COUNT {10};
  uint16_t sum {0};
  for (uint8_t i = 0; i < COUNT; ++i) {
    ADCSRA |= _BV(ADSC);                 // Start conversion
    while ((ADCSRA & _BV(ADSC))) { ; }   // measure
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { sum += ADCW; }
  }
  sum = sum / COUNT;
  soSerial.print("ADC: ");
  soSerial.println(sum);
  return static_cast<uint16_t>(INERNAL_REFxRESOLUTION / sum);
}

void setup() {
  soSerial.begin(9600);
  soSerial.println(F("Start"));
  initADC0();   // Set ADC to interal reference voltage as input (Vbg)
}

void loop() {
  // static char buffer[sizeof(uint16_t) + 1];
  static char buffer[5];
  // uint16_t val =  measurementVCC();
  // buffer[0] = val >> 8;
  // buffer[1] = val;
  // buffer[2] = 0;
  // soSerial.print(buffer);
  // soSerial.print("\n");

  itoa(measurementVCC(), buffer, DEC);
  soSerial.print(buffer);
  soSerial.print("\n");
  delay(2000);
}

So sieht der Schaltungsaufbau aus:

Der Microcontroller ist auf eine interne Taktfrequenz von 8Mhz eingestellt.

Hallo,

so einen ähnlichen Effekt hatte ich 2014 auch festgestellt. Ub mit AREF messen
Es sind zwei 1,1V Referenzen im Spiel und scheinbar ist die Kennlinie dieser Dioden nicht steil genug und damit stärker vom Strom abhängig als einem lieb ist, was die angelegte Ub bewirkt. Und weil man rückwärts rechnet macht sich ein kleiner Fehler stärker bemerkbar.
Dir bleibt im Grunde nur diesen einen bestimmten Controller von oben bis unten zu vermessen und in einer Tabelle zu hinterlegen. Oder man leidet aus dem auseinanderdriften eine Korrekturformel ab. Excel mit Polynom bilden kann einem dabei helfen.
Mit dem Improving Accuracy kommt man auch nicht weit.

Eine andere Erklärung habe ich dafür nicht. Ich habe das mittlerweile als netten Versuch abgehakt. Wenn es genau sein soll entweder aufwendig abgleichen usw. oder extern messen. Falls ich doch etwas übersehen habe bin ich ganz Ohr.

1 Like

Schon mal daran gedacht, dass das Verfahren dort fehlerhaft sein könnte?

Warum ist das Verfahren falsch?
Ganz einfach: Es werden die 1.10 Volt schon in der Rechnung mit einbezogen.
Das ist ein logischer Fehler, damit unsinnig und wohl zu falschen Werten/Ergebnissen führt.

Ich habe ja auch mal ein Messgerät gebaut. Dabei bin ich davon ausgegangen, dass sich bei deiner Ref. Spannung von 1,1 Volt die Ungenauigkeit der Ref. vervierfacht, im Vergleich zu meiner verwendeten Ref. Spannung 4,096 Volt. Das habe ich damals nicht getestet, sondern das sagte mir einfach die Logik.

Ob ich da richtig liege :thinking: :roll_eyes: weiß ich nicht, habe es nie mit der kleinen Ref. Spannung versucht. Habe daher keinen Vergleich.

Franz

Hört sich komisch an....

Dass die interne Referenz nicht sonderlich genau ist, steht im Datenblatt. (fertigungsbedingt +-10% + weitere Abhängigkeiten)
Deine 4,096 Volt ist sicherlich genauer, auch hier ist das Datenblatt das Maß der Dinge.

Wie du da allerdings auf 4 mal kommst, ist mir ein Rätsel.

Ja, wie gesagt vielleicht ist meine "Logik" unlogisch :roll_eyes:

Aber wenn ich mit einer Ref. Spannung von 1,1 Volt 5 Volt messe, wird doch der Fehler mehr als vervierfacht, weil die falsche Ref. 4x reingeht.
Eine Ref. von 4.096 Vot geht einmal rein. Also wird die Abweichung der Ref.Spannung nicht vervielfacht.

@Doc_Arduino:
Danke für den Link und die Antwort. Zumindest hilft mir das mal insofern weiter, dass das ich mit den Aufbau nicht völlig daneben liege und das scheinbar ein "prinzipbedingter" Fehler ist.

@combie:
Ich habe nicht wirklich daran gedacht, allerdings hat mich der Ansatz den 1.1V Wert fest vorzugeben zumindest stutzen lassen. Allerdings gibt es viele Leute die schlauer sind als ich. Darum habe ich das erst mal so übernommen.

Letztendlich habe ich jedoch die "Referenzwerte" für den höchsten und die niedrigsten Spannungswert durch ausprobieren ermittelt. Ich habe das im Eröffnungsbeitrag etwas falsch geschrieben, weil ich das nach dem ganzen Ausprobieren gedanklich durcheinander gebracht habe.

Ich glaube mit dem Referenzprogramm hatte ich irgendwas von 993 ausgegeben bekommen (was mir schon als viel zu niedrig erschien). Egal. Das hat nicht ganz gepasst war, aber bei den 5V schon nahe dran. Weil ich aber nicht immer die Programme wechseln wollte, habe ich den Wert so lange angepasst, bis die Ausgabe mit der Multimeteranzeige mögl. genau übereingestimmt hat.

Das habe ich, nachdem ich die großen Abweichungen bei anderen Betriebsspannungen festgestellt habe, bei 3V wiederholt und die Messreihe erstellt. Von daher ist das Programm welches die Referenzspannung ermitteln soll da eigentlich raus.

Nun habe ich das gleiche Experiment noch mit einem ATTiny1604 durchgeführt. Da funktioniert das Ganze interessanter Weise recht gut. Die Abweichung pro Messpunkt 3V, 3,5V, 4V, 4,5V, 5V liegt im einstelligen Millivoltbereich. Sogar, wenn ich den mit dem "Referenzwertbestimmungs-Programm" ermittelten Wert nehme. Das ist, im positiven Sinne, nicht vergleichbar mit dem Käse, der bei dem ATTiny85 herauskommt.

Beim ATtiny85 kann man nur die 1,1V entsprechend "schalten". Eine andere Referenzspannung steht da für diesen "Trick" nicht zur Verfügung.

Das dürfte falsch sein!

  1. Mit einer 1,1V Ref kann man keine 0 bis 5V quantisieren! (ohne Spannungsteiler)
  2. Der Spannungsteiler, mit seiner Widerstandstoleranz, geht als zusätzlicher Fehler ein, lässt sich aber problemlos kompensieren, da stabil

Deine 5/1,1=4 mit der 4 als Fehlerfaktor findet sich da nirgendwo in der Physik

Im ursprünglich verlinkten Thread wurde der Wert der Referenz in einem vorherigen Sketch bei gemessener Betriebsspannung pro IC ermittelt und dieser Wert dann in die Rechnung einbezogen.

Gruß Tommy

Ja, natürlich mit Spannungsteiler. Das ist schon klar. Ohne Spannungsteiler ist das Ergebiss vielleicht mit dem Ruspartickelmesser zu erfassen.

Genau diese Rechnung!


  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;
  float rvcc = 1.10 * 1023L / result;
  float internal1_1Ref = 1.1 * VCC / rvcc;
  Serial.print("Interne Referenz (1.1V): ");Serial.println(internal1_1Ref);

  1. Was macht die magische Zahl 1.1 in der Rechnung, wo kommt die her?
  2. Wieso 1023 und nicht 1024?

Nein, die Rechnung ist falsch/unlogisch!

Die Rechnung sieht für mich so aus, als währe sie per Zufallsgenerator entstanden.

Gut!
Und wo stecken jetzt deine ungenauigkeit*4 ?

Ja, da bin ich jetzt auch gerade dabei. Ich rechne ja nicht wie oft 1,1 Volt in meinen Messwert gehen, sondern 1,1 ist 1023. Da hast du recht. :thinking: Wurde gerade überzeugt, dass logik nicht logisch sein muss, wenn man drüber nachdenkt. :crazy_face:

ja.

Logik ist immer logisch.

Allerdings, wenn man in seine Logik falsche Annahmen einbaut, dann ist auch das Ergebnis der ansonsten korrekten Logik logischerweise falsch.

Und der akzeptiert auch am REF - Eingang keine andere Referenzspannung? Denn ich mache ja meine Ref-Spannung 4,096 Volt selber.

Ja, habe mir den ATtiny85 gerade angesehen. Der hat natürlich keinen externen REF. Da hätte ich natürlich auch gleich nachschauen können, was das für ein Teil ist, dann hätte ich mir die Frage sparen können. :flushed:

Bei der ganzen Nummer geht es ja darum, bei einem Microcontroller die Betriebsspannung zu ermitteln, ohne einen Pin dafür zu verwenden. Das ist für den Batteriebetrieb interessant.

Wenn ich in diese Formel float internal1_1Ref = 1.1 * VCC / rvcc; das rvcc ersetze wird daraus:
float internal1_1Ref = (1.1 * VCC) / (1.10 * 1023L / result);

Umgestellt 1:
float internal1_1Ref = (1.1 * VCC * result) / (1.10 * 1023L );

Gekürzt: 1.1 kann weg
float internal1_1Ref = (VCC * result) / 1023L;

Umgestellt 2: um die Berechnung zu optimieren
float internal1_1Ref = (VCC / 1023.0) * result;

Korrigiert, da es 1024 verschiedene Values gibt:
float internal1_1Ref = (VCC / 1024.0) * result;

So!
So ist das meiner bescheidenen Ansicht nach richtig!

Und jetzt noch:

uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;

Unnötig kompliziert!
Und damit unnötig fehlerträchtig

Warum nicht gleich so:?

long result = ADCW;
oder:
long result = ADC;

oder gleich in die Formel rein
 float internal1_1Ref = (VCC / 1024.0) * ADCW;
1 Like

Doch hat er!
Pin PB0 ist dafür zuständig.

Macht aber hier keinen Sinn.