Spannung messen mit dem Arduino ProMini

Wie bereits erwähnt, bin ich befasse ich mich momentan mit der Thematik Spannung mit dem Arduino ProMini möglichst genau zu messen. Ich verwende für meine Nodes sehr gerne einen Arduino ProMini in der 3.3 Volt Variante. Dieser verwendet einen ATmega328p, weshalb ich auch nur auf diesen Chip direkt eingehen werde.

Den Profis unter euch ist das hier dargestellt sicherlich nicht neu, da es vielerorts bereits erwähnt wird. Ich hatte damit bereits mal Versuche gestartet, bin dabei aber komplett vom Weg abgekommen.

Warum sind genaue Spannungsmessungen wichtig?

Viele Sensoren liefen an ihrem Ausgang eine Spannung, proportional zur messenden Größe. Wir müssen also eine Spannung messen um auf den eigentlich gemessenen Wert umrechnen zu können. Je genauer also unsere Spannungsmessung ist, desto genauer ist auch unser Messergebnis. Wer also genau messen möchte, ist auch darauf angewiesen.

Was ist jedoch das Problem bei Spannungsmessungen?

Normalerweise verwenden wir analogRead() um die Werte an einem analogen Eingang auszulesen. Dabei wird die Eingangsspannung in Werten zwischen 0 und 1023 ausgegeben, in Abhängigkeit von der Versorgungsspannung Vcc. Daraus lässt sich dann auch die anliegende Spannung berechnen:

int ADC;
float Voltage;

ADC = analogRead(A0);
Voltage = (ADC / 1024.0) * 3.3;

Hier liegt das eigentliche Problem. Meistens kennen wir die Versorgungsspannung Vcc nicht genau und nehmen meistens den ideellen Wert 3,3V an. Damit verfälschen wir dann unsere Messergebnisse. Ich nutze zwar Spannungsregler um meine Nodes zu betreiben, aber auch die haben eine 5% Toleranz. So können aus 3,3V schnell 3,4V werden. Dazu kommt noch das die Spannung schwanken kann. Wir müssen also immer unsere Versorgungsspannung wissen, sobald wir analogRead() nutzen.

Präziser messen mit der Bandgab Voltage

Glücklicherweise besitzt der 328p eine eigene 1,1V Referenzspannung, die so genannte Bandgap Voltage. Diese ist sehr genau und kaum abhängig von Temperatur oder Versorgungsspannung. Durch diese Referenz können wir relativ genau unsere Versorgungsspannung messen und eben auch die Spannung an unseren Eingängen berechnen. Folgernder Code hilft dabei:

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(10); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

Unser Code zur Berechnung der Spannung kann dann so aussehen:

int ADC;
float Vcc;
float Voltage;

Vcc = readVcc()/1000.0;
ADC = analogRead(A0);
Voltage = (ADC / 1024.0) * Vcc;

Damit sind wir eine genauen Messung schon um einiges näher gekommen.

Darf es noch genauer sein?

Die Bandgap Voltage kann zwischen 1.0 und 1.2 V liegen, ist dort aber stabil. Wir können nun unseren ideellen Wert 1125300L noch anpassen um diese Schwankung zu kompensieren. Dazu müssen wir, wie oben beschrieben, unsere Versorgungsspannung messen und zusätzlich noch mit einem Multimeter. Anschließend können wir den Korrekturfaktor berechnen:

Vmultimeter / Varduino = Korrekturfaktor

Den Faktor multipliziert ihr nun mit 1125300 und tauscht den Wert in readVcc() aus. Dieser Faktor ist für jeden Chip einmalig, ihr müsst ihn also für jeden neu bestimmen. Der Lohn eurer Arbeit ist jedoch eine noch genauere Spannungsmessung mit eurem ProMini. Der ganze Sketch kann so aussehen:

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(10); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println( readVcc(), DEC );
  delay(1000);
}

Das ganze Thema ist natürlich nicht meinem Hirn entsprungen, sondern wurde z.B. hier bereits beschrieben.

Nun nochmals alles zusammen

Wenn man nun alles zusammen nimmt, kann ein Sketch zum auslesen von A0 so aussehen:

float Vcc;
float Vbatt;
int AdcBatt;

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(10); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Vcc = readVcc()/1000.0;
  AdcBatt = analogRead(A0);
  Vbatt = (AdcBatt / 1024.0) * Vcc;
  Serial.println(Vbatt,2);
  delay(1000);
}

Es gibt auch noch Quellen, die empfehlen auf den Wert ADC noch 0,5 aufzuaddieren um möglichst genau zu sein:

float Voltage = ((ADC + 0.5) / 1024.0) * Vcc;

Um die Werte noch etwas zu glätten, kann man nun auch noch den Mittelwert aus mehreren Messungen berechnen. Dafür habe ich alles um die Funktion readVoltage() ergänzt:

float Vcc; 
float Vbatt;
float AdcBatt;

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(10); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

long readVoltage(uint8_t pin){
  long result;

  int i = 0;
  for (i = 0; i < 10; i++) {
    AdcBatt += analogRead(pin);
    delay(2);
  }
  AdcBatt += (i+1) * 0.5;
  AdcBatt /= (i+1);
  result = AdcBatt;
  return result;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Vcc = readVcc()/1000.0;
  AdcBatt = readVoltage(0);
  Vbatt = (AdcBatt / 1024.0) * Vcc;
  Serial.println(Vbatt, 2);
  delay(1000);
}

Was bringt es?

Ich habe einen kleinen Testaufbau zusammengestellt und dabei die einzelnen Messwerte ermittelt. Ich messe dabei die Spannung einer LiIon-Zelle über einen Spannungsteiler. :

Art der Messung Spannung
Batterie direkt 3,63
Messpunkt Spannungsteiler1,81 (3,62)
Messung über Vcc=3,3V3,54
Messung über readVcc()3,60
Messung über readVcc() angepasst3,62

Anfänglich haben wir noch 0,09V unterschied und später nur noch 0,01V. Das ist jedenfalls schon ein großer Unterschied, der viel ausmachen kann. Betrachtet man auch die Spannung am Messpunkt des Spannungsteilers, sind die Werte sogar genau.

5 Gedanken zu „Spannung messen mit dem Arduino ProMini“

  1. Vielen Dank für die tolle, verständliche Zusammenstellung.
    So habe auch ich es tatsächlich kapiert. Bin gerade dabei einen kleinen Datenlogger zu bauen, um mal meine, in die Jahre gekommenen Akkus zu testen.

    In der Funktion „readVoltage“ ist, glaube ich ein kleiner Fehler.
    Der Mittelwert wird mit einem „i“ zu wenig gerechnet.
    Ich habe die Zeile „AdcBatt /= i;“ durch „AdcBatt /= (i + 1);“ ersetzt.

    Das Rauschen hält sich auch in Grenzen.
    Für den Privatgebrauch ist das wohl okay.
    https://imgur.com/CSi9QD9

    Schöne Grüße,
    Patrick

    Antworten
  2. Hallo Björn,

    ich der Zeile davor habe ich eigentlich nur die “ * 0.5″ entfernt.
    Komme so näher an meine Referenz.

    Also im Prinzip sieht es bei mir jetzt so aus:
    long readVoltage(uint8_t pin){
    long result;

    int i = 0;
    for (i = 0; i < 10; i++) {
    AdcBatt += analogRead(pin);
    delay(5);
    }
    AdcBatt += i;
    AdcBatt /= (i + 1);
    result = AdcBatt;
    return result;
    }

    Schöne Grüße,
    Patrick

    Antworten

Schreibe einen Kommentar