mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Vcc und andere Spannungen mit Atmega messen


Autor: Jens Krause (jjk) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo µC.net Leser,

ich habe vor mit einem Mega8 3 Verschiedene Spannungen (Eingang 12-20V, 
Ausgang 5V und 9V) zu messen für eines meiner Projekte[1]. Unter anderem 
eben die Versorgungsspannung des Mega8, welcher am 5V Ausgangszweig des 
unter [1] verlinken Projektes hängt.

Nun sind mir da ein paar Fragen aufgekommen, die ich gerne vor der 
Hardwarebestellung geklärt hätte. Ich werde, wie im Datenblatt 
vorgeschlagen, ein LC Netzwerk an den AVcc Pin packen und auch eine 
Analoge Ground Plate nutzen.

Um die Vcc zu messen werde ich einen hier im Forum gefunden Tip[2] 
nutzen, und über die interne Bandgab-Referenz auf Vcc schliessen.

Nun bleiben noch die beiden Spannungen Input und 9V, die muß ich ja dann 
auf die Referenzspannung skalieren.
Und genau da wäre jetzt meine Frage, wenn mich bei der Eingangsspannung 
der Bereich von 12-20Volt interessieren würde, dann müßte ich doch den 
Spannungsteiler für den Input am Mega8 so bauen, dass bei 12V Eingang, 
am Mega8 ~0V ankommen und bei 20V eben AVcc (5Volt oder intern 2,56V ist 
hier noch eine Frage)? Richtig soweit? Das gleiche dann bei den 9Volt 
(Bereich 7,5V-10,5V).

Also wäre die Rechnung für die Eingangsspannung bei folgendem ST
 o-----------+
 Input       |
 Uin        +-+
            | | R1
            +-+ 
             |
             +---------o AVR (Uref)
             |
            +-+
            | | R2
            +-+
             |
 o-----------+
in etwa:
mit den Werten
Uin = 20V, Uref = 5V und R2 = 1kOhm
macht das also für R1 = 3,2kOhm und das müßte dann passen, oder?

Dann wäre noch die Frage ob es sinnvoll wäre Vcc als AVcc zu nehmen oder 
besser die interne. Und kann das prinzipiell so funktionieren? 
Theoretisch hätte ich ja die genaue Vcc ermittelt über den 
Bandgab-Referenz Trick und könnte für die anderen Messungen mit diesem 
Wert rechnen.

Vielleicht merkt man dass das mein erstes ADC Projekt ist, ich teste das 
hier mit meinem Evaluationsboard auch soweit das geht, aber so eine 
Bestätigung dass man nicht auf dem Holzweg ist, wäre schön. Zumindest 
wüßte ich dann ob ich die Theorie verstanden habe und das auch so 
klappen könnte ...

Gruß
  Jens


[1] Beitrag "2 Spannungen, rel. hohe Leistung"
[2] Beitrag "AVR: Vcc gegen interne Referenz messen möglich?"

Autor: Walter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>bei 12V Eingang,
>am Mega8 ~0V ankommen

da kommen natürlich nicht 0V an ...

für den Spannungsteiler gilt:

Uref = Uzumessen * R2 / (R1+R2)

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

>Dann wäre noch die Frage ob es sinnvoll wäre Vcc als AVcc zu nehmen oder
>besser die interne.

Ich glaube du meinst etwas anderes.

>Und genau da wäre jetzt meine Frage, wenn mich bei der Eingangsspannung
>der Bereich von 12-20Volt interessieren würde, dann müßte ich doch den
>Spannungsteiler für den Input am Mega8 so bauen, dass bei 12V Eingang,
>am Mega8 ~0V ankommen und bei 20V eben AVcc (5Volt oder intern 2,56V ist
>hier noch eine Frage)?

Das geht mit einem Spannungsteiler so nicht. 20V nach Vref ja. 8V nach 
0V nein (oder 20V auch 0V) .

>Theoretisch hätte ich ja die genaue Vcc ermittelt über den
>Bandgab-Referenz Trick und könnte für die anderen Messungen mit diesem
>Wert rechnen.

Theoretisch. Kannst du sicher sein, das VCC noch gleich dem Wert ist, 
den du zuvor mit der Messung gegen die Referenz ermittelt hast?.

VCC als Referenzspannung ist nur dann sinnvoll wenn sich die zu messende 
Spannung im gleichen Maße wie VCC ändert. Z.B. du willst du die Stellung 
eines Potentiometers einlesen, das auch an VCC hängt.

VCC-unabhängige Messwerte erreichst du nur durch Benutzung der internen, 
oder bei höheren Anforderungen einer externen Referenz.

MfG Spess

Autor: Jens Krause (jjk) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mhm, also heißt das ich kann nicht nur einen Bereich "raussuchen" mit 
dem Spannungsteiler, und habe, wenn meine maximal Eingangsspannung 20V 
ist, beim 10-Bit ADC des Mega8, eine Auflösung von
? Naja, damit könnte ich auch leben.

Ich möchte die drei Spannungen ja nur anzeigen, also nix wildes. Werd 
das einfach mal ausprobieren so.

Vielen Dank schonmal
  Jens

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jens Krause schrieb:
> Mhm, also heißt das ich kann nicht nur einen Bereich "raussuchen" mit
> dem Spannungsteiler,

Drum heißt das Ding ja auch Spannungs/teiler/ und nicht 
Spannungs/subtrahierer/

> und habe, wenn meine maximal Eingangsspannung 20V
> ist, beim 10-Bit ADC des Mega8, eine Auflösung von
> 20V/1024 = 0,01953V?

Ganz genau

Autor: Jens Krause (jjk) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So, heut morgen kamen die 1% Widerstände für die Spannungsteiler. Also 
die anliegende Spannung am ADC kann ich ziemlich genau auf mein LCD 
bringen, habs noch nicht auf die Eingangsspannung umgerechnet. Danke 
nochmal für die Zaunpfahlwinkerei g ...

Gruß
  Jens

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: Jens Krause (jjk) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
So, ich habs geschafft alle drei Spannungen anzuzeigen (siehe Anhang).

Ich hab jetzt aber mal ne C-Frage, vielleicht ist das ne ganz dumme 
Anfängerfrage (kannte bis jetzt nur Java), aber ich hab etwas nicht so 
hinbekommen wie ich wollte.

Hier mal mein kompletter Quellcode:
#include <avr/io.h>
#include <util/delay.h>
#include "lcd-routines.h"
#include <avr/interrupt.h>
#include <stdlib.h>

void init_timer();
void showWelcomeMessage();
void showVoltageFrame();
void adcinit();
uint16_t getadc(uint8_t channel);
uint16_t convert9Voltage(uint16_t adc);
uint16_t convert20Voltage(uint16_t adc);
uint16_t convert5Voltage(uint16_t adc);
void my_uitoa(uint32_t zahl, char* string);
void my_print_LCD(char* string, uint8_t start, uint8_t komma, uint8_t frac);

#define TIMEOUT 5

volatile uint8_t doWelcomeTimeout;
volatile uint8_t timerCounter;
volatile uint8_t welcomeTimeOut;
volatile char state;
// state0 = welcomemessage
// state1 = voltage display

void init_timer() {
  // Timer0 settings: ~ 64000 ticks (16 ms)
  TCCR0 = (1<<CS02);
  // prescaler = 256
  // init counter
  TCNT0 = 6;
  TIMSK = (1<<TOIE0);
  // Timer0 Overflow Interrupt Enable

}

int main(void) {

  DDRD |= (1 << PD5) | ( 1 << PD6);
  // PD5 und 6 Ausgänge
  lcd_init();
  state = 0;
  timerCounter = 0;
  showWelcomeMessage();
  _delay_ms(2000);
  showVoltageFrame();
  init_timer();
sei  ();
      uint16_t val;
  char str;
  while (1) {
    val = getadc(0);
    set_cursor(0, 1);
    my_uitoa(convert9Voltage(val), &str);
    set_cursor(12, 2);
    my_print_LCD(&str, 6, 7, 2);
    val = getadc(1);
    my_uitoa(convert20Voltage(val), &str);
    set_cursor(10, 1);
    my_print_LCD(&str, 5, 7, 2);
    val = getadc(2);
    my_uitoa(convert5Voltage(val), &str);
    set_cursor(7, 2);
    my_print_LCD(&str, 6, 7, 2);
    _delay_ms(500);
  }
  return 0;
}

void showWelcomeMessage() {
  set_cursor(0, 1);
  lcd_string("FPV-Power-Watch ");
  set_cursor(0, 2);
  lcd_string("(c)       jjk 09");
}

void showVoltageFrame() {
  set_cursor(0, 1);
  lcd_string("Input     --,--V");
  set_cursor(0, 2);
  lcd_string("5V/9V  -,--/-,--");
}

// Timer0 Overflow every 16ms
ISR(TIMER0_OVF_vect)
{
  if(timerCounter == 62) {
    timerCounter = 0;
    // eine Sekunde ist um
  }
  timerCounter++;
  // reinit counter
  TCNT0 = 6;
}

void adcinit() {
  // Activate ADC with Prescaler 16 --> 1Mhz/16 = 62.5kHz
  //ADCSRA = _BV(ADEN) | _BV(ADPS2);
  ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1);
  ADMUX= 0;
}

uint16_t getadc(uint8_t channel) {
  uint8_t i;
  uint16_t result;
  adcinit();
  // Select pin ADC0 using MUX
  ADMUX = channel | _BV(REFS0) | _BV(REFS1);

  ADCSRA |= (1<<ADSC);
  // eine ADC-Wandlung 
  while ( ADCSRA & (1<<ADSC)) {
    ; // auf Abschluss der Konvertierung warten 
  }
  result = ADCW; // ADCW muss einmal gelesen werden,
  // sonst wird Ergebnis der nächsten Wandlung
  // nicht übernommen.

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

  result /= 4;
  return result;
}

uint16_t convert9Voltage(uint16_t adc) {
  return (adc * 9000L) / 1024L;
}

uint16_t convert20Voltage(uint16_t adc) {
  return (adc * 20000L) / 1024L;
}

uint16_t convert5Voltage(uint16_t adc) {
  return (adc * 5000L) / 1024L;
}

void my_uitoa(uint32_t zahl, char* string) {
  int8_t i; // schleifenzähler

  string[10]='\0'; // String Terminator
  for (i=9; i>=0; i--) {
    string[i]=(zahl % 10) +'0'; // Modulo rechnen, dann den ASCII-Code von '0' addieren
    zahl /= 10;
  }
}

/*
 
 Funktion zur Anzeige einer 32 Bit Zahl im Stringformat
 auf einem LCD mit HD44780 Controller
 
 Parameter:
 
 char* string  : Zeiger auf String, welcher mit my_itoa() erzeugt wurde
 uint8_t start : Offset im String, ab der die Zahl ausgegeben werden soll,
 das ist notwenig wenn Zahlen mit begrenztem Zahlenbereich
 ausgegeben werden sollen
 Vorzeichenlose Zahlen      : 0..10
 Vorzeichenbehaftete zahlen : 1..11
 uint8_t komma : Offset im String, zeigt auf die Stelle an welcher das virtuelle
 Komma steht (erste Nachkommastelle)
 komma muss immer grösser oder gleich start sein !
 
 uint8_t frac  : Anzahl der Nachkommastellen
 
 */

void my_print_LCD(char* string, uint8_t start, uint8_t komma, uint8_t frac) {

  uint8_t i; // Zähler
  uint8_t flag=0; // Merker für führende Nullen

  // Vorzeichen ausgeben  
  // if (string[0]=='-') lcd_data('-'); else lcd_data(' ');

  // Vorkommastellen ohne führende Nullen ausgeben
  for (i=start; i<komma; i++) {
    if (flag==1 || string[i]!='0') {
      lcd_data(string[i]);
      flag = 1;
    } else
      lcd_data(' '); // Leerzeichen
  }

  lcd_data(','); // Komma ausgeben

  // Nachkommastellen ausgeben
  for (; i<(komma+frac); i++)
    lcd_data(string[i]);

}

Nun wollte ich eigentlich die Funktion
uint16_t convert2Voltage(uint16_t adc, uint16_t maxvoltage) {
  return (adc * maxvoltage) / 1024L;
}
benutzen und dann, z.B. für 9V mit
convert5Voltage(val, 9000L);
aufrufen.
Aber das funktionierte nicht. Nun dacht ich mir, da klappt was mit dem 
casten auf den unsigned int nicht, hab es aber nicht hinbekommen ... 
Lieg ich da richtig? Und wenn ja, wie könnte ich das lösen?

Jens

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jens Krause schrieb:

> Nun wollte ich eigentlich die Funktion
>
> uint16_t convert2Voltage(uint16_t adc, uint16_t maxvoltage) {
>   return (adc * maxvoltage) / 1024L;
> }
> 
> benutzen und dann, z.B. für 9V mit
>
> convert5Voltage(val, 9000L);
> 
> aufrufen.
> Aber das funktionierte nicht. Nun dacht ich mir, da klappt was mit dem
> casten auf den unsigned int nicht,


Du kannst die 9000 long machen so viel du willst. Das ändert nichts 
daran, dass hier
uint16_t convert2Voltage(uint16_t adc, uint16_t maxvoltage) {

maxvoltage ein 16 Bit Datentyp ist, ebenso wie adc. Deine 9000L werden 
bei der Übergabe sofort wieder zu 16 Bit eingedampft.

Daher wird hier
adc * maxvoltage

16-Bit Arithmetik betrieben. Und 1024 * 9000 ergäbe 9216000, welches 
wiederrum viel zu groß für ein 16 Bit Ergebnis ist.

Hier
uint16_t convert9Voltage(uint16_t adc) {
  return (adc * 9000L) / 1024L;
}

liegt die Sache anders. Wenn du nur
adc * 9000L
betrachtest, dann wird hier ein uint16_t, also ein 16 Bit Datentyp mit 
einem long, also einem 32 Bit Datentyp, multipliziert. Und in dem Fall 
gewinnt der größere Datentyp -> die Multiplikation wird als 32 Bit 
Multiplikation ausgeführt. Und in 32 Bit ist 9216000 ohne Probleme 
darstellbar.
uint16_t convert2Voltage(uint16_t adc, uint32_t maxvoltage) {
  return (adc * maxvoltage) / 1024;
}

...

   mitconvert5Voltage(val, 9000);


-> Bei jeglicher Rechnerei, bei jeder Operation, immer überlegen: 
welcher Datentyp steht links von der Operation, welcher rechts davon. 
Der größere der beiden gewinnt und in diesem Datentyp kriegst du auch 
das Ergebnis der Operation. Das kann dann wieder zb die linke oder 
rechte Seite einer anderen Operation sein usw. oder aber auch das 
Endergebnis welches dann einer Variablen zugewiesen wird, wobei es dann 
vor der Zuweisung wieder auf den Datentyp der Zielvariable umgewandlt 
wird.

Das ist ein häufiger Trugschluss:

  uint32_t result;
  uint16_t a, b;

  result = a * b;

und die Frage dazu: Hilfe, warum klappt die Berechnung nicht? Ich weise 
ja ohnehin an eine 32 Bit Variable zu!

Schon. Das ist aber für die Berechnung an sich uninteressant. Links vom 
* steht ein 16 Bit Wert, rechts davon steht ein 16 Bit Wert. Also wird 
die Multiplikation als 16 Bit Multiplikation durchgeführt (welche 
überläuft) und erst dann das fehlerhafte Ergebnis auf 32 Bit gebracht.

Autor: Jens Krause (jjk) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank für die ausführliche Antwort!

Gruß
  Jens

Autor: Jens Krause (jjk) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
So, nun ist alles in einem Gehäuse vereint :-)

Jens

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.