PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : nibobee: Akkuspannung mit ADC messen



Skroete
31.12.2009, 17:51
Hallo,

ich habe mich in letzter Zeit mit der Spannungsmessung der Batterien beschäftigt und möchte meine Erkenntnisse hier zur Diskussion stellen.

Meiner Meinung nach funktioniert die Akku-Spannungsmessung so wie sie in der Nibobeelib implementiert ist nicht.
Für die Messung der analogen Signale wird die AVcc als Referenzspannung benutzt, auch für die Messung der Akkuspannung. Da sich ein Spannungsteiler von 2*47K (R43,R44) am Eingang ADC4 (=VBAT) befindet, wird man immer den Wert von ca. 512 als Resultat der Akkuspannung erhalten.
Das kommt daher, dass der ADC immer den Wert 1024 für den Vollausschlag für die Spannung an AVcc heranzieht. Woher soll also der ADC wissen, dass die Akkuspannung und damit AVcc in die Knie geht?
Will man also die Akkuspannung messen, so braucht man eine Referenzspannung an AVcc, die konstant ist und von der Akkuspannung unabhängig ist. Dazu kann man die interne 2.56V Referenzspannung im ATMega16 benutzen.
Ich hoffe, dass ich das bis hierher richtig wiedergegeben habe und möchte mal nachfragen, ob ihr das genauso seht.

Nun habe ich hier mal ein kleines Programm geschrieben, das auf diese Weise zu den richtigen Ergebnissen führt. Da ich nicht in der Nibobeelib herumdoktern wollte, ist es gewissermassen ein "Workaround", um den Fehler zu korrigieren ohne die anderen ADKanäle zu stören oder zu verändern.
Vielleicht wird ja der Fehler in einer neuen Nibobeelib behoben.


int Batterie(void)
{
unsigned char temp1,temp2;
uint16_t ADCresult;

while ((ADMUX & 0x07) != 0x04);

cli();

while (!(ADCSRA & (1 << ADIF))); // wait for conversion complete
ADCSRA |= (1 << ADIF); // clear ADCIF

//Registerinhalte retten
temp1 = ADCSRA;
temp2 = ADMUX;

ADMUX = (1 << REFS0) | (1 << REFS1) | ANALOG_VOLT; //internal 2.56V reference with external capacitor

//ADCSRA löschen und neu setzen
ADCSRA = 0;
ADCSRA |= ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0)); //ADC clock = Clock/128
ADCSRA |= (1 << ADEN); //Enable ADC (das aktiviert die 2.56V Referenz)

//Warten bis Kondensator an ARef = 2.56V hat
//Messung an ARef ergab 1,2msec
delay(6); // = ca. 5*Tau

//1. ADC Wandlung starten und Ergebnis ignorieren
ADCSRA |= (1 << ADSC); // Start conversion
while (!(ADCSRA & (1 << ADIF))); // wait for conversion complete
ADCSRA |= (1 << ADIF); // clear ADCIF

//2. ADC Wandlung starten und Ergebnis übernehmen
ADCSRA |= (1 << ADSC); // Start conversion
while (!(ADCSRA & (1 << ADIF))); // wait for conversion complete
ADCSRA |= (1 << ADIF); // clear ADCIF

ADCresult = ADCL + (ADCH << 8);

//Registerinhalte wiederherstellen
ADMUX = temp2;
ADCSRA = temp1 & ~(1 << ADSC);

//Warten bis Kondensator an ARef = AVcc hat
//Messung ergab sehr steile Flanke
delay(2); //nicht notwendig, nur zur Sicherheit

ADCSRA |= (1 << ADSC); // Start conversion

sei();

return ADCresult;
}



Nun bin ich mal auf eure antworten gespannt.

Viele Grüsse
Skroete

radbruch
01.01.2010, 16:29
Hallo

Sollte nach dem Zurückschalten auf die 5V-Referenz nicht auch noch eine Dummylesung erfolgen? Wieso funktioniert delay() bei gesperrten Interrupts? Wäre es nicht günstiger nur den ADC-Interrupt zu verhindern anstatt alle Interrupts zu sperren?

Oh, da ist ja noch ein Klassiker: Im Datenblatt des Mega16 in der Beschreibung von ADIF (Seite 219):


Alternatively, ADIF is cleared by writing a logical one to the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled.Bedeutet ungefähr: Wenn das Flag nicht automatisch durch den Aufruf der ISR gelöscht wird, kann man es auch "von Hand" löschen. Aber man darf dabei keine "Read-Modify-Write"-Anweisung auf das ADSRA-Register anwenden. das bedeutet, das Flag sollte so gelöschte werden:

ADCSRA = (1 << ADIF);

Das gilt auch für manche andere Flags im Zusammenhang mit den Interrupts!

Gruß

mic

Besserwessi
01.01.2010, 16:45
Noch dem Umschalten der Ref. Spannung muß man damit rechnen das die AD Wandlung nicht richtug geht. Das gilt vor allem für den Schritt on 5 V auf 2,5 V . Da kann es relativ lange dauern bis ein Kondensator an ARef entladen ist. Da besser nur 10 nF statt der sonst üblichen 100 nF nehmen.

markusj
01.01.2010, 17:32
Es gibt noch eine andere Möglichkeit, die Akkuspannung zu messen, die ich hier im RN gelesen habe:
Als Referenz wird VCC gewählt, und als zu messender Kanal die interne Referenzspannungsquelle von 1,1V oder 2,56V.
Die tatsächliche Spannung errechnet sich dann durch Ref * Auflösung / Messwert, bei vollen 10 Bit Auflösung und 1,1V gewählter interner Referenzzspannung wäre dann Vbat = 1,1V * 1024 / ADC

mfG
Markus

Skroete
01.01.2010, 18:13
Hallo,

eigentlich hatte ich gehofft, dass sich jemand mal äussert, ob ihr auch der Meinung seit, dass die Messung der Akkuspannung nicht funktioniert, so wie sie in der Nibobeelib implementiert ist.

Hier mal die Antworten auf die Fragen von radbruch.
Sollte nach dem Zurückschalten auf die 5V-Referenz nicht auch noch eine Dummylesung erfolgen?
Antwort:
Das wird auch gemacht. Nach der Messung der Batteriespannung in der neuen Routine, wird die Interruptgetriebene ADC Wandlung mit einem erneuten messen der Batteriespannung wieder aufgenommen. Allerdings mit der Falschen ARef Spannung. Damit ist das Ergibnis sowieso Schrott aber es wurde eine Dummy-Messung durchgeführt.
Deshalb warte ich in der Batterie-Routine bis der Kanal 4 (VBAT) dran ist, rette die Register, schreibe sie wieder zurück und mache genauso weiter als ob die Batterie-Routine nie aufgerufen worden wäre.

Wieso funktioniert delay() bei gesperrten Interrupts?
Antwort:
Die delay(..) stützt sich auf der _delay_ms aus <util/delay.h> ab. Die arbeitet mit Zeitschleifen und braucht keinen Interrupt (siehe avr-libc-users-manual).

22.28 <util/delay_basic.h>: Basic busy-wait delay loops
22.28.1 Detailed Description
#include <util/delay_basic.h>
The functions in this header file implement simple delay loops that perform a busy waiting.
They are typically used to facilitate short delays in the program execution.
They are implemented as count-down loops with a well-known CPU cycle count per loop iteration. As such, no other processing can occur simultaneously. It should be kept in mind that the functions described here do not disable interrupts.
In general, for long delays, the use of hardware timers is much preferrable, as they free the CPU, and allow for concurrent processing of other events while the timer is running. However, in particular for very short delays, the overhead of setting up a hardware timer is too much compared to the overall delay time.
Two inline functions are provided for the actual delay algorithms.
Functions
• void _delay_loop_1 (uint8_t __count)
• void _delay_loop_2 (uint16_t __count)

Wäre es nicht günstiger nur den ADC-Interrupt zu verhindern anstatt alle Interrupts zu sperren?
Antwort:
Richtig


Oh, da ist ja noch ein Klassiker: Im Datenblatt des Mega16 in der Beschreibung von ADIF (Seite 219):

Zitat:
Alternatively, ADIF is cleared by writing a logical one to the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled.
Bedeutet ungefähr: Wenn das Flag nicht automatisch durch den Aufruf der ISR gelöscht wird, kann man es auch "von Hand" löschen. Aber man darf dabei keine "Read-Modify-Write"-Anweisung auf das ADSRA-Register anwenden. das bedeutet, das Flag sollte so gelöschte werden:

ADCSRA = (1 << ADIF);

Das gilt auch für manche andere Flags im Zusammenhang mit den Interrupts!

Antwort:
Das oben gesagte bedeutet nur, dass man wissen muss, was man tut, bedeutet aber nicht, dass man es nicht tun darf.
Ich lösche ADIF bevor ich ADCSRA in temp1 rette. Ich lösche ADIF am Ende meiner eingefügten Batteriespannungsmessung und somit einen (eventuell) pending ADC Interrupt. Eigentlich ist das aber nur zur Sicherheit, denn in der Initialisierung der Batteriespannungsmessung wird ADIE in ADCSRA bewusst nicht gesetzt. Also sollte ADIF eigentlich gar nicht kommen. Beim Zurückschreiben von temp1 in ADCSRA weiss ich also ganz genau, was ich zurückschreibe.
Ich lösche auch ganz bewusst ADSC in ADCSRA beim zurückschreiben, damit die delay(2) auch wirklich abgearbeitet werden können, bevor ich danach mit ADSC=1 den gewohnten ADC Betrieb wieder auf nehme.

Viele Gruesse
Skroete