AdSense

Sonntag, 31. August 2014

Spannung überwachen am Arduino, ATmega oder ATtiny

(English version)
Wer seinen Arduino oder einen ATmega bzw. ATtiny über eine Batterie mit Spannung versorgt muss immer damit rechnen, dass die irgendwann einmal leer ist und dann der Mikrocontroller ohne Vorwarnung den Dienst einstellt. Hier ist es hilfreich, die Versorgungsspannung zu überwachen. Sollte sie unter einen bestimmten kritischen Wert fallen, kann man sich zum Beispiel durch anschalten einer LED warnen lassen.
Die Überwachung der Spannung kann ohne zusätzliche Hardware erfolgen. Möglich macht es der AD-Wandler. Dieser arbeitet ja bekannterweise nach folgender Formel:
WERT_ADC = V_EINGANG * 1023/V_REFERENZ
Nun könnte man natürlich meinen: "Ich lege einfach meine Versorgungsspannung an den Eingang des AD-Wandlers und kann diese so messen". Das funktioniert leider nicht, da die Referenzspannung des AD-Wandler ebenfalls die Versorgungsspannung ist. Der Wert des AD-Wandlers wäre als immer 1023.
Doch es gibt einen Trick: jeder Mikrocontroller erzeugt eine interne Referenzspannung, die so genannte "Bandgap Voltage" V_BG. Diese beträgt beim ATmega328, der im Arduino Uno zum Einsatz kommt, 1.1V.  Diese Spannung lässt sich auf den Eingang des AD-Wandlers legen. mit V_EINGANG = V_BG und V_REFERENZ = V_CC ergibt sich dann:
WERT_ADC = V_BG * 1023/V_CC
oder umgeformt:
V_CC = V_BG * 1023/WERT_ADC
Setzt man nun ein V_BG = 1100 (in mV) ergibt sich:
V_CC = 1100 * 1023/WERT_ADC
V_CC ist nun der Wert der Spannungsversorgung in mV

Umgesetzt in Code sieht das dann so aus:

int led = 13;  //Pin der LED
int adc_low, adc_high;  //Zwischenspeicher für die Ergebnisse des ADC
long adc_result;  //Gesamtergebnis der Messung des ADC
long vcc;  //Versorgungsspannung

void setup() {    
  pinMode(led, OUTPUT);  

  ADMUX |= (1<<REFS0); //VCC als Referenzspannung für den AD-Wandler
  ADMUX |= (1<<MUX3) | (1<<MUX2) | (1<<MUX1);  //1.1V Referenzspannung als Eingang für ADC 
  delay(10);  //warten bis sich die Referenzspannung eingestellt hat
  ADCSRA |= (1<<ADEN);   //ADC aktivieren
}

// the loop routine runs over and over again forever:
void loop() {    
  ADCSRA |= (1<<ADSC);  //Messung starten

  while (bitRead(ADCSRA, ADSC));  //warten bis Messung beendet ist
  //Ergebnisse des ADC zwischenspeichern. Wichtig: zuerst ADCL auslesen, dann ADCH
  adc_low = ADCL;
  adc_high = ADCH;

  adc_result = (adc_high<<8) | adc_low; //Gesamtergebniss der ADC-Messung 
  vcc = 1125300L / adc_result;  //Versorgungsspannung in mV berechnen (1100mV * 1023 = 1125300)

  //wenn Spannung kleiner als 5V 
  if (vcc < 5000)
  {
    digitalWrite(led, HIGH);  //schalte LED an
  }
  //wenn größer oder gleich 5V
  else 
  {
    digitalWrite(led, LOW);  //schalte LED aus
  }

  delay(500);
}


In der setup()-Funktion wird zuerst der AD-Wandler so eingestellt, dass zum einen die Versorgungsspannung V_CC als Referenz und zum anderen die Bandgap-Spannung V_BG als Eingang für den AD-Wandler dienen. Nach einer kurzen Wartezeit wird der AD-Wandler dann aktiviert.
In der loop() wird dann die Messung durch setzen des Bits ADSC gestartet. Ist die Messung beendet (ADSC ist wieder 0) wird das Ergebnis der Wandlung aus den Registern ADCL (die unteren 8 Bit) und ADCH (die oberen 2 Bit) ausgelesen und zu einem 10-Bit-Ergebnis zusammengesetzt. Aus diesem wird dann nach der obigen Formel die Versorgungsspannung bestimmt. Liegt sie unter 5V, wird eine LED eingeschaltet.

Diese Methode funktioniert prinzipiell mit allen ATmegas oder ATtinys. Je nach verwendetem µC müssen allerdings in den Registern des AD-Wandlers andere Bits gesetzt werden. Welche das genau sind, kann dem Datenblatt entnommen werden.

20 Kommentare:

  1. Hallo Udo

    das ist ja eine interessannte Funktion. Ich habe sie mal ausprobiert allerdings kommen da sehr stark schwankende Werte heraus.
    Das ligt , so denke ich , an den Registern die ich noch nicht an meinen AT-Mega 328P angepasst habe.
    Da ich in der Programierung noch nicht so bewandert bin dies leider noch nicht beheben können.
    Auch eine Internetrecherche hat mir nicht weitergeholfen.
    Könntest Du mir diese Register nennen die ich eindstellen muss.

    Vielen Dank Rudi

    AntwortenLöschen
    Antworten
    1. Hallo Rudi,

      die Register sollten eigentlich identisch sein. Inwiefern schwanken die Werte?

      Viele Grüße,

      Timo

      Löschen
  2. Hallo Udo,
    ich fange gerade erst an µC zu programmiren und habe mir ein kleines Projekt als Ziel vorgenommen. Unter anderem muss mein Arduino auch seine Versorgungsspannung messen (1S LiPo) und mir die momentan anliegende Spannung mittels 5 LED's anzeigen.
    Dein oben beschriebenes Programm überblicke ich soweit (nicht bis ins Detail) habe aber doch Probleme dieses für den Ardu Nano umzusetzen.
    Könnetst Du mir dabei ein bisschen unter die Arme greifen?
    Gruß
    Jens

    AntwortenLöschen
    Antworten
    1. Hi,

      ja klar. Bei was brauchst du denn genau Hilfe?

      Gruß, Udo

      Löschen
  3. Hallo Udo,

    ich hatte Dir eine Email geschrieben. Ist die angekommen?

    Gruß
    jens

    AntwortenLöschen
  4. Hallo Udo!
    Das ist eine interessante Idee.
    Wie funktioniert das beim Arduino Pro mini mit 3,3V?
    Die Spannung wird mit 1100mV gemessen - müssen die Register anders eingestellt werden?

    Grüße,
    Chris

    AntwortenLöschen
  5. Hallo Udo,
    bei mir kommt da beim Arduino Uno immer raus vcc=2241, sowohl am USB als auch beim Anschluß der Batterien. Wie kann das sein?

    Grüße
    Max

    AntwortenLöschen
  6. Hallo Udo,
    dein Programm funktioniert bei mir auf einem Mega2560 einwandfrei (zeigt zwar bei Betrieb an USb ca. 3800an, aber vorerst nicht so wichtig). Kopiert auf ein Nano (ATmega 328)habe ich bei ADCL =0 bei ADCH=0 und beim Ergebnis vcc= -1
    Wo könnte der Unterschied sein?
    Gruß Axel

    AntwortenLöschen
  7. Hallo bei meinem ATtiny45 werden 1100mv gemessen, müssten die Register anders eingestellt werden und wie mache ich das?

    AntwortenLöschen
  8. Hallo Udo,
    Ich habe dein Programm für die Spannungsüberwachung von meinem Arduino UNO verwendet, bekomme aber keine sinnvollen Ergebnisse. Vermutlich muss ich in den Registern des AD Wandlers Änderungen vornehmen. Ich habe bereits mehrere Datenblätter angesehen, wurde aber leider auch nicht schlauer. Mein µC wäre der Atmega 328P-PU.
    Ich wäre sehr froh, wenn du mir dabei helfen könntest!

    AntwortenLöschen
  9. Hi, vielen Dank für den Code, funktioniert bei mir problemlos. Du könntest vll. noch hinzufügen, dass nach einem "analogRead()" der Code im Setup erneut ausgeführt werden muss, um die Betriebsspannung wieder messen zu können.

    Ich habe dazu deinen Code in Setup und Loop zusammen in eine readVcc()-Funktion gepackt, die nun problemlos gemischt mit analogRead() verwendet werden kann.

    lg, couka

    AntwortenLöschen
    Antworten
    1. Hi,

      habe den setup und loop Teil auch in eine Funktion ausgelagert.
      Funktioniert prima, zusammen mit analogRead() liefert es aber nur -1.
      Um es trotzdem mit analogRead() zu verwenden musste ich noch den ADLAR Bit auf 0 setzen.

      Gruß, Sebastian

      Löschen
  10. Sorry aber sobald AREF auf 1,1V liegt dann ist die maximal messbare Spannung ebenfalls 1,1V. Es kann also nie eine Batteriespannung von 3,3V o.ä. direkt gemessen werden, sondern es muss immer noch ein Spannungsteiler eingesetzt werden, der die Batteriespannung in den messbaren Bereich (0V -> 1,1V) bringt. Dann geht's aber!

    AntwortenLöschen
  11. Hallo Udo, super interressant alles. Ich würde gerne die Betriebsspannung von einem Lipo 1s mit einem Attiny85 messen. Ich habe das Programm mal auf einen Arduino Nano geladen da funktioniert es perfekt. Beim Attiny messe ich leider immer die 1,1 V. Du schreibst ja auch was von anderen Bits für Attinys, ich kann mir da nur leider nicht viel darunter vorstellen.
    Vielen dank im Vorraus.
    Gruß Gösta

    AntwortenLöschen
  12. Hallo Udo, ich nutze das Programm derzeit an meinem Arduino Pro Mini und es funzt wunderbar. Ich stell mir nur gerade die Frage weshalb eigentlich denn die Versorgungsspannung liegt nicht direkt am Atmega an denn dieser wird über einen davorgeschalteten Spannungsregler versorgt (wie auch bei anderen Boards...)...mach ich da einen Denkfehler ?
    Grüße
    Micha

    AntwortenLöschen
  13. Super klappt so einfach wie es aussieht. Arduino mit einem 3,7VLipo betreiben bei 3,4V Warnung bei 3,3 Abschaltung. Mit einem Laderegler (MCP73831T-2ACI/OT) wird der Akku wieder geladen und geht nicht mehr kaputt. DANKE!

    AntwortenLöschen
  14. Hallo,
    ich habe für meinen Heizungsraum einen kleinen Gasalarm programmiert,
    dieser soll aber auch Alarm schlagen, wenn die Spannung der Battarie zu Niedrig wird.
    Ich habe ihn erst heute in den Dienst geschickt und hatte daher so eine Situation noch nicht.
    Im moment mache ich einen analogRead von a3, der mit dem vin pin verbunden ist.
    Geht das so, wie ich es programmiert habe?

    #include

    int State = 0;
    LiquidCrystal lcd(11, 12, 22, 23, 24, 25);

    void setup() {
    pinMode(A1, INPUT);
    pinMode(A3, INPUT);
    pinMode(22, OUTPUT);
    pinMode(23, OUTPUT);
    pinMode(24, OUTPUT);
    pinMode(25, OUTPUT);
    pinMode(26, OUTPUT);
    pinMode(27, OUTPUT);
    pinMode(28, INPUT);
    lcd.begin(16, 2);
    Serial.begin(9600);
    int Alarm_State = 0;
    }
    void loop() {
    Serial.println(analogRead(A1));
    Serial.println(analogRead(A3));
    if(State == 0){
    lcd.setCursor(0, 0);
    lcd.write("Alarm Gas");
    lcd.setCursor(0, 1);
    lcd.write(" ");
    lcd.setCursor(6, 1);
    lcd.write("kein Alarm");
    digitalWrite(27, HIGH);
    digitalWrite(26, LOW);
    }

    if(analogRead(A1) > 400){
    State = 1;
    }
    if(analogRead(A3) < 1023 and State == 0){
    State = 2;
    }
    if(State == 1){
    digitalWrite(26, HIGH);
    digitalWrite(27, LOW);
    lcd.setCursor(0, 1);
    lcd.write("Alarm,Gashahn zu");
    }
    if(State == 2){
    digitalWrite(26, HIGH);
    digitalWrite(27, LOW);
    lcd.setCursor(0, 1);
    lcd.write("Batterie leer ");
    delay(500);
    digitalWrite(26, LOW);
    delay(10000);
    }
    }

    AntwortenLöschen
  15. Hallo Udo,

    danke für Deine Arbeit.
    Dass Programm funktioniert wie es soll, zeigt mir aber die Spannung hinter dem Spannungsregler des Arduinos an (also knappe 5V).
    Gibt es auch irgendeinen Weg die Spannung VOR dem Regler zu messen? Ich Betreibe den Arduino mit 9V und würde gerne erkennen, wenn die Batteriespannung abfällt.

    Gruß Jens

    AntwortenLöschen
    Antworten
    1. oder ich nehme einen Spannungsteiler um wieder auf die max 5V zu kommen

      Löschen
  16. Tolles Programm, aber beim Attiny85 funktioniert es leider gar nicht. Vielleicht fehlt noch eine Anpassung für diesen Chip.

    AntwortenLöschen