Versorgungsspannung ohne zusätzliche Bauteile messen

Die Methode ist nicht auf meinem Mist gewachsen. Google liefert eine ganze Reihe von Links. Google versorgungsspannung+messen+AVR+OR+atmega oder Google atmega+batteriespannung+messen, wo die Methode mehr oder weniger kompliziert erklärt wird. Hier ein konkreter Link in www.mikrocontroller.net.

Beim „normalen“ ADC-Messvorgang wird eine unbekannte Spannung, die an einem der ADC-Eingänge anliegt, gegen eine bekannte Referenzspannung gemessen. Bei der Messung der Versorgungsspannung (VCC) wird VCC als unbekannte Referenzspannung gegen die bekannte interne Bandgap-Spannungsquelle gemessen.

 Diese interne Spannungsquelle liefert recht präzise 1,1 oder 2,56 Volt, je nach Prozessortyp. Das Datenblatt für den ATmega328 (Arduino) macht folgende Angaben:

Table 29-12. Reset, Brown-out and Internal Voltage Characteristics

Symbol Parameter Min. Typ Max Units
...            
VBG Bandgap reference voltage VCC=2.7
TA=25°C
1.0  1.1  1.2  V
tBG Bandgap reference start-up time VCC=2.7
TA=25°C
  40 70 µs
IBG Bandgap reference current consumption VCC=2.7
TA=25°C
  10   µA

 Bei www.schramm-software.de habe ich folgende Tabelle gefunden, die Auskunft über die Qualität der internen Referenzspannung gibt:

Betr.spg.\ Controller 2,5 V 3,0 V 3,8 V 5,0 V
ATtiny13A   1,10 V 1,08 V 1,07 V
ATtiny13A   1,10 V 1,09 V 1,07 V
ATtiny13V 1,07 V 1,08 V 1,08 V 1,08 V
ATtiny44V 1,10 V 1,10 V 1,10 V 1,10 V

Die Tabelle zeigt, dass mit einer besseren Genauigkeit gerechnet werden kann, als im Datenblatt angegeben ist. Bei meinem Arduino Uno habe ich 1,105 V gemessen.

Da Referenz- und Eingangsspannung bei dieser Methode die Rollen tauschen, muss die Formel zur Berechnung der Werte umgestellt werden. Die im Datenblatt angegebene Formel

ADC = Vin * 1024 / Vref (Erwartungswert für ADC bei Vref = 5 V und Vin = 1,1 V: ca. 225)

wird zu

Vref = Vin * 1024 / ADC mit Vin = 1,1 V für den ATmega328 (Arduino).

Die folgenden Angaben zur Registereinstellung gelten für ATmega48 / 88 / 168 / 328: Zur Messung muss REFS1/REFS0 mit 01 belegt werden. VCC dient damit als Referenzspannung (Genau genommen wird AVCC zugeschaltet. In den meisten Schaltungen liegt jedoch AVCC auf dem gleichen Potential wie VCC).

Table 24-3. Voltage Reference Selections for ADC

REFS1 REFS0 Voltage Reference Selection
0 0 AREF, Internal Vref turned off
0 1 AVCC with external capacitor at AREF pin
1 0 Reserved
1 1 Internal 1.1V Voltage Reference with external capacitor at AREF pin

Die interne Referenzspannung (1,1 Volt) wird über den Eintrag 1110 am ADC-Input-Multiplexer zur Verfügung gestellt.

Table 24-4. Input Channel Selections

MUX3...0 Single Ended Input
0000 ADC0
0001 ADC1
...  
1101 (reserved)
1110 1.1V (VBG)
1111 0V (GND)
 

Die Messung erfolgt dann wie üblich. Wie der Wert für den ADC-Prescaler ermittelt werden kann ist hier beschrieben (ADC-Timing). Allerdings dauert es eine Weile, bis sich die interne Bandgap-Spannungsquelle genügend stabilisiert hat. Einige Dummy-Wandlungen vor der eigentlichen Messung sind angemessen (s. www.mikrocontroller.net/topic/98469#853068). Man kann die Wartezeit natürlich auch auf andere Art und Weise verbringen :-).

Hier ein komplettes Arduino-Programm:

/*
 * Arduino AVR Project
 *
 * Vcc messen
 *
 * Created: 2015-04-27
 *  Author: Ulli
 *
 * 2024-02-20: Registerwerte für weitere CPUs hinzugefügt
 *
 */ 

#include "Arduino.h"
#include "AppVersion.h"

// Prescaler ermitteln 
#define ADC_CLK_MIN 50000UL 
#define ADC_CLK_MAX 200000UL 
#include "ADC-Timing.h"

#define DummyReads 10 // Anzahl der Dummy-Readouts nach Hinzuschalten der Bandgap-Referenz zu deren Stabilisierung
#define Vbg 1105UL    // Bandgap-Spannung in mV
#define Samples 4     // Anzahl Messungen zur Mittelwertbildung
uint16_t ReadVcc();   // List die Betriebsspannung mit einer Genauigkeit von 10 mV aus.

// the setup routine runs once when you press reset:
void setup() {
  Serial.begin(9600);
}

// the loop routine runs over and over again forever:
void loop() { 
  Serial.print("Vcc: ");
  Serial.println((float)ReadVcc() / 1000);
  delay(2000);
}

uint16_t readVcc() { // Ergebnis: in mV
// Kanal, Referenz und Bandgap-Spannung für die verschiedenen CPUs
#if defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny44__)
   ADMUX = _BV(MUX5) | _BV(MUX0);
   int Vbg = 1100; // Bandgap-Spannung
#elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny45__)
   ADMUX = _BV(MUX3) | _BV(MUX2);
   int Vbg = 1100; // Bandgap-Spannung
#elif defined(__AVR_ATmega1284P__)
   ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
   int Vbg = 1100; // Bandgap-Spannung
#elif defined(__AVR_ATmega328__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega48__)|| defined(__AVR_ATmega88__)
   ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
   int Vbg = 1100; // Bandgap-Spannung
#elif defined(__AVR_ATmega8__)
   ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
   int Vbg = 1300; // Bandgap-Spannung
#else
   #error Not defined for this CPU
#endif

   uint8_t i;
   uint16_t result = 0;
   ADCSRA = (1 << ADEN) | ADC_PRESCALER_MASK; // ADC einschalten

   // Dummy-Readout zur Stabilisierung der Bandgap-Spannungsquelle
   for (i = 0; i < DummyReads; i++)
   {
      ADCSRA |= (1 << ADSC);        // eine ADC-Wandlung
      while (ADCSRA & (1 << ADSC)); // auf Abschluss der Konvertierung warten
   }

   // Eigentliche Messung - Mittelwert aus 4 aufeinanderfolgenden Wandlungen
   for (i = 0; i < Samples; i++)
   {
      ADCSRA |= (1 << ADSC);         // eine Wandlung "single conversion"
      while (ADCSRA & (1 << ADSC)); // auf Abschluss der Konvertierung warten
      result += ADCW;               // Ergebnisse aufaddieren
   }

   ADCSRA = 0; // ADC wieder ausschalten

   uint32_t r = Vbg * 1024 * Samples / result + 5;
   r = r / 10;    // Runden auf 10mV
   return r * 10;
}

Das Ergerbnis:

Vcc: 4.95
Vcc: 4.96
Vcc: 4.94