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


von Jens K. (jjk) Benutzerseite


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
1
 o-----------+
2
 Input       |
3
 Uin        +-+
4
            | | R1
5
            +-+ 
6
             |
7
             +---------o AVR (Uref)
8
             |
9
            +-+
10
            | | R2
11
            +-+
12
             |
13
 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?"

von Walter (Gast)


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)

von spess53 (Gast)


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

von Jens K. (jjk) Benutzerseite


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

von Karl H. (kbuchegg)


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

von Jens K. (jjk) Benutzerseite


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

von Falk B. (falk)


Lesenswert?


von Jens K. (jjk) Benutzerseite


Angehängte Dateien:

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:
1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include "lcd-routines.h"
4
#include <avr/interrupt.h>
5
#include <stdlib.h>
6
7
void init_timer();
8
void showWelcomeMessage();
9
void showVoltageFrame();
10
void adcinit();
11
uint16_t getadc(uint8_t channel);
12
uint16_t convert9Voltage(uint16_t adc);
13
uint16_t convert20Voltage(uint16_t adc);
14
uint16_t convert5Voltage(uint16_t adc);
15
void my_uitoa(uint32_t zahl, char* string);
16
void my_print_LCD(char* string, uint8_t start, uint8_t komma, uint8_t frac);
17
18
#define TIMEOUT 5
19
20
volatile uint8_t doWelcomeTimeout;
21
volatile uint8_t timerCounter;
22
volatile uint8_t welcomeTimeOut;
23
volatile char state;
24
// state0 = welcomemessage
25
// state1 = voltage display
26
27
void init_timer() {
28
  // Timer0 settings: ~ 64000 ticks (16 ms)
29
  TCCR0 = (1<<CS02);
30
  // prescaler = 256
31
  // init counter
32
  TCNT0 = 6;
33
  TIMSK = (1<<TOIE0);
34
  // Timer0 Overflow Interrupt Enable
35
36
}
37
38
int main(void) {
39
40
  DDRD |= (1 << PD5) | ( 1 << PD6);
41
  // PD5 und 6 Ausgänge
42
  lcd_init();
43
  state = 0;
44
  timerCounter = 0;
45
  showWelcomeMessage();
46
  _delay_ms(2000);
47
  showVoltageFrame();
48
  init_timer();
49
sei  ();
50
      uint16_t val;
51
  char str;
52
  while (1) {
53
    val = getadc(0);
54
    set_cursor(0, 1);
55
    my_uitoa(convert9Voltage(val), &str);
56
    set_cursor(12, 2);
57
    my_print_LCD(&str, 6, 7, 2);
58
    val = getadc(1);
59
    my_uitoa(convert20Voltage(val), &str);
60
    set_cursor(10, 1);
61
    my_print_LCD(&str, 5, 7, 2);
62
    val = getadc(2);
63
    my_uitoa(convert5Voltage(val), &str);
64
    set_cursor(7, 2);
65
    my_print_LCD(&str, 6, 7, 2);
66
    _delay_ms(500);
67
  }
68
  return 0;
69
}
70
71
void showWelcomeMessage() {
72
  set_cursor(0, 1);
73
  lcd_string("FPV-Power-Watch ");
74
  set_cursor(0, 2);
75
  lcd_string("(c)       jjk 09");
76
}
77
78
void showVoltageFrame() {
79
  set_cursor(0, 1);
80
  lcd_string("Input     --,--V");
81
  set_cursor(0, 2);
82
  lcd_string("5V/9V  -,--/-,--");
83
}
84
85
// Timer0 Overflow every 16ms
86
ISR(TIMER0_OVF_vect)
87
{
88
  if(timerCounter == 62) {
89
    timerCounter = 0;
90
    // eine Sekunde ist um
91
  }
92
  timerCounter++;
93
  // reinit counter
94
  TCNT0 = 6;
95
}
96
97
void adcinit() {
98
  // Activate ADC with Prescaler 16 --> 1Mhz/16 = 62.5kHz
99
  //ADCSRA = _BV(ADEN) | _BV(ADPS2);
100
  ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1);
101
  ADMUX= 0;
102
}
103
104
uint16_t getadc(uint8_t channel) {
105
  uint8_t i;
106
  uint16_t result;
107
  adcinit();
108
  // Select pin ADC0 using MUX
109
  ADMUX = channel | _BV(REFS0) | _BV(REFS1);
110
111
  ADCSRA |= (1<<ADSC);
112
  // eine ADC-Wandlung 
113
  while ( ADCSRA & (1<<ADSC)) {
114
    ; // auf Abschluss der Konvertierung warten 
115
  }
116
  result = ADCW; // ADCW muss einmal gelesen werden,
117
  // sonst wird Ergebnis der nächsten Wandlung
118
  // nicht übernommen.
119
120
  /* Eigentliche Messung - Mittelwert aus 4 aufeinanderfolgenden Wandlungen */
121
  result = 0;
122
  for (i=0; i<4; i++) {
123
    ADCSRA |= (1<<ADSC);
124
    // eine Wandlung "single conversion"
125
    while ( ADCSRA & (1<<ADSC)) {
126
      ; // auf Abschluss der Konvertierung warten
127
    }
128
    result += ADCW; // Wandlungsergebnisse aufaddieren
129
  }
130
  ADCSRA &= ~(1<<ADEN);
131
  // ADC deaktivieren (2)
132
133
  result /= 4;
134
  return result;
135
}
136
137
uint16_t convert9Voltage(uint16_t adc) {
138
  return (adc * 9000L) / 1024L;
139
}
140
141
uint16_t convert20Voltage(uint16_t adc) {
142
  return (adc * 20000L) / 1024L;
143
}
144
145
uint16_t convert5Voltage(uint16_t adc) {
146
  return (adc * 5000L) / 1024L;
147
}
148
149
void my_uitoa(uint32_t zahl, char* string) {
150
  int8_t i; // schleifenzähler
151
152
  string[10]='\0'; // String Terminator
153
  for (i=9; i>=0; i--) {
154
    string[i]=(zahl % 10) +'0'; // Modulo rechnen, dann den ASCII-Code von '0' addieren
155
    zahl /= 10;
156
  }
157
}
158
159
/*
160
 
161
 Funktion zur Anzeige einer 32 Bit Zahl im Stringformat
162
 auf einem LCD mit HD44780 Controller
163
 
164
 Parameter:
165
 
166
 char* string  : Zeiger auf String, welcher mit my_itoa() erzeugt wurde
167
 uint8_t start : Offset im String, ab der die Zahl ausgegeben werden soll,
168
 das ist notwenig wenn Zahlen mit begrenztem Zahlenbereich
169
 ausgegeben werden sollen
170
 Vorzeichenlose Zahlen      : 0..10
171
 Vorzeichenbehaftete zahlen : 1..11
172
 uint8_t komma : Offset im String, zeigt auf die Stelle an welcher das virtuelle
173
 Komma steht (erste Nachkommastelle)
174
 komma muss immer grösser oder gleich start sein !
175
 
176
 uint8_t frac  : Anzahl der Nachkommastellen
177
 
178
 */
179
180
void my_print_LCD(char* string, uint8_t start, uint8_t komma, uint8_t frac) {
181
182
  uint8_t i; // Zähler
183
  uint8_t flag=0; // Merker für führende Nullen
184
185
  // Vorzeichen ausgeben  
186
  // if (string[0]=='-') lcd_data('-'); else lcd_data(' ');
187
188
  // Vorkommastellen ohne führende Nullen ausgeben
189
  for (i=start; i<komma; i++) {
190
    if (flag==1 || string[i]!='0') {
191
      lcd_data(string[i]);
192
      flag = 1;
193
    } else
194
      lcd_data(' '); // Leerzeichen
195
  }
196
197
  lcd_data(','); // Komma ausgeben
198
199
  // Nachkommastellen ausgeben
200
  for (; i<(komma+frac); i++)
201
    lcd_data(string[i]);
202
203
}

Nun wollte ich eigentlich die Funktion
1
uint16_t convert2Voltage(uint16_t adc, uint16_t maxvoltage) {
2
  return (adc * maxvoltage) / 1024L;
3
}
benutzen und dann, z.B. für 9V mit
1
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

von Karl H. (kbuchegg)


Lesenswert?

Jens Krause schrieb:

> Nun wollte ich eigentlich die Funktion
>
1
> uint16_t convert2Voltage(uint16_t adc, uint16_t maxvoltage) {
2
>   return (adc * maxvoltage) / 1024L;
3
> }
4
>
> benutzen und dann, z.B. für 9V mit
>
1
> convert5Voltage(val, 9000L);
2
>
> 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
1
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
1
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
1
uint16_t convert9Voltage(uint16_t adc) {
2
  return (adc * 9000L) / 1024L;
3
}

liegt die Sache anders. Wenn du nur
1
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.
1
uint16_t convert2Voltage(uint16_t adc, uint32_t maxvoltage) {
2
  return (adc * maxvoltage) / 1024;
3
}
4
5
...
6
7
   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.

von Jens K. (jjk) Benutzerseite


Lesenswert?

Vielen Dank für die ausführliche Antwort!

Gruß
  Jens

von Jens K. (jjk) Benutzerseite


Angehängte Dateien:

Lesenswert?

So, nun ist alles in einem Gehäuse vereint :-)

Jens

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.