Forum: Mikrocontroller und Digitale Elektronik Erstversuch mit ATmega32 ADC


von Jens K. (mister232)


Lesenswert?

Hallo Leute,

ich versuch mich gerade das erste Mal am ADC des ATmega32. Beschaltet 
habe ich den ATmega wie folgt:

AVCC -> Direkt an VCC = 3,3V
AREF -> Mit einem 100nF Kondensator an GND
ADC0 -> Potentiometer zwischen VCC und GND, am Mittelabgriff hängt ADC0

Parallel zum ADC, also zwischen ADC0 und GND hängt noch ein Multimeter, 
mit dem ich die Spannung am ADC-Eingang kontrollieren kann. Hiermit 
überwache ich auich, dass die Spannung nicht über 2,56V geht (interne 
Referenzspannung gewählt).

Der Code auf dem ATmega sieht wie folgt aus:

Die Main-Datei:
1
#include "uart.h"
2
#include "ADC.h"
3
4
#include <avr/io.h>
5
#include <util/delay.h>
6
#include <string.h>
7
#include <stdio.h>
8
9
int main(void)
10
{
11
  uint16_t adc_val;
12
  double volt_val;
13
  char buf[50];
14
  
15
  // Initialize the UART
16
  init_UART(9600);
17
  // Initialize the ADC
18
  init_ADC();
19
  
20
    while(1)
21
    {
22
        // Get a new voltage value
23
        adc_val = readNewValueFromADC();
24
    
25
    sprintf(buf, "The actual adc value is: %d \n\r", adc_val);
26
    uart_write_msg(buf,strlen(buf));
27
    
28
    _delay_ms(1000);
29
    }
30
}

Und die ADC-Datei:
1
#include <avr/io.h>
2
#include "ADC.h"
3
#include "uart.h"
4
5
uint16_t ADC_Read( uint8_t channel );
6
7
// Function to initialize the ADC
8
void init_ADC(void)
9
{
10
  uint16_t result;
11
  
12
  // Select the reference voltage source (internal reference)
13
  ADMUX = (1<<REFS0) | (1<<REFS1);
14
  
15
  // Select prescaler to clk/64
16
  ADCSRA = (1<<ADPS2) | (1<<ADPS1);
17
  
18
  // Enable the ADC
19
  ADCSRA |= (1<<ADEN);
20
  
21
  // Do one conversion, for start-up of the ADC
22
  ADCSRA |= (1<<ADSC);
23
  // Wait for conversion complete
24
  while(ADCSRA & (1<<ADSC)){}
25
  
26
  // T>Dummy read first  adc-value
27
  result = ADCW;
28
}
29
30
// Function to read a new value from the ADC
31
uint16_t readNewValueFromADC(void)
32
{  
33
  // Select channel 0
34
  ADMUX = (ADMUX & ~(0x1F)) | (0 & 0x1F);
35
  
36
  // Single conversion
37
  ADCSRA |= (1<<ADSC);
38
  // Wait for conversion complete
39
  while (ADCSRA & (1<<ADSC) ) {}
40
    
41
  // Return ADC-Value
42
  return ADCW;
43
}

Im Prinzip ein ganz einfaches Programm, dass mir auf der Konsole am PC 
jede Sekunde den aktuellen ADC-Wert anzeigen soll.
Leider pendelt der ausgegebene Wert einfach nur um 470 herum, also 460, 
475, 455, 483, ... unabhängig davon ob ich am Potentiometer drehe.

Wo liegt mein Fehler?

von Dietrich L. (dietrichl)


Lesenswert?

Jens K. schrieb:
> Wo liegt mein Fehler?

Was mir spontan einfällt:
- Wie stabil ist Vcc? Das liefert Dir ja über das Poti den Messwert.
- Ist Vcc mit Stützkondenstor abgeblockt?
- Wie hochohmig ist das Poti? Lt. Datenblatt sollte die Quellimpedanz 
max. 10kOhm sein. Eventuell auch einen Filterkondensator an ADC0 hängen.
- "Wilder" Gesamtaufbau / Masseschleifen ...

Die Initialisierung, besonders den Prescaler habe ich jetzt nicht 
kontrolliert.

Gruß Dietrich

von Jens K. (mister232)


Lesenswert?

- VCC ist recht stabil, wird von einem Linearregler erzeugt
- VCC hat einen 100nF Stützkondensator
- Das Poti hat maximal 5K
- Hatte ich vergessen: zwischen ADC0 und GND hängt auch ein 100nF 
Kondensator
- Der Aufbau ist zwar auf einem Steckbrett, aber an sich relativ 
ordentlich würde ich sagen.

Außerdem müsste man ja, selbst bei Störeinkopplungen, Änderungen am 
ADC-Wert sehen, wenn ich am Poti drehe. Es treten aber immer die 
besagten Werte auf, unabhängig von der Position des Potentiometers.

von Jens S. (jens_s56)


Lesenswert?

Mehrere Messungen auf addieren und durch die Anzahl teilen,
dann hast Du per Software den Wert gemittelt.
Oft kommt es ja nicht darauf an, ob eine Spannung z.B.
4,72 V oder 4,74 V oder 4,67 Volt ist.
Gerundet ergibt sich halt 4,7 Volt.

: Bearbeitet durch User
von Dietrich L. (dietrichl)


Lesenswert?

Jens K. schrieb:
> Es treten aber immer die
> besagten Werte auf, unabhängig von der Position des Potentiometers.

Das hattest Du ja schon geschrieben, ich aber leider überlesen ;-(
Dann fällt mir nichts mehr ein, höchsten Kontaktprobleme am Steckbrett. 
Hast Du die ADC0-Spannung mal direkt am Pin des µC gemessen?

Gruß Dietrich

von Jens K. (mister232)


Lesenswert?

Ich habe jetzt mal eine Mittlung über 10 Werte eingefügt. Leider 
schwankt das Ergebnis immer noch unabhängig von der Stellung des 
Potentiometers.

von Jens K. (mister232)


Lesenswert?

Jetzt funktioniert es! Ganz dummer Fehler:

Es war tatsächlich ein Kontaktproblem. Der Pin ADC0 des ATmega´s ist 
abgebrochen und steckte somit garnicht im Board :-)

Danke für den Tipp Dietrich!

von Dietrich L. (dietrichl)


Lesenswert?

Jens K. schrieb:
> Jetzt funktioniert es!

Das ist schön.

> Danke für den Tipp Dietrich!

Bitte ;-))

von Bastler (Gast)


Lesenswert?

>Ich habe jetzt mal eine Mittlung über 10 Werte eingefügt.

Das ist etwas suboptimal.
Nimm 8  16  32 dann kann der Compiler den aufsummierten Wert einfach 
nach rechts shiften. Das spart Zeit.

von Thomas E. (thomase)


Lesenswert?

Jens K. schrieb:
> Parallel zum ADC, also zwischen ADC0 und GND hängt noch ein Multimeter,
> mit dem ich die Spannung am ADC-Eingang kontrollieren kann. Hiermit
> überwache ich auich, dass die Spannung nicht über 2,56V geht (interne
> Referenzspannung gewählt).

Um ein Poti einzulesen nimmt man Vcc als Referenz. Dann hast du immer 
den vollen Wertebereich  von Anschlag zu Anschlag. Die Spannung ist 
dabei egal, da das Verhältnis der beiden Widerstände bestimmt wird. Und 
das ist unabhängig von der Spannung immer gleich.

mfg.

von Joachim B. (jar)


Lesenswert?

Bastler schrieb:
> Das ist etwas suboptimal.
> Nimm 8  16  32 dann kann der Compiler den aufsummierten Wert einfach
> nach rechts shiften. Das spart Zeit.

genau

mal mein Codeschnipsel
1
  #define READS 3 
2
  // oder 4 oder 5 nach belieben
3
  uint16_t result=0;
4
  for(i=0; i<READS; i++) 
5
  {  // Eine Wandlung
6
    ADCSRA |= (1<<ADSC);
7
    // Auf Ergebnis warten...
8
    while(ADCSRA & (1<<ADSC));
9
10
    result += ADCW;  // aufsummieren
11
  }
12
  result >>= READS;    // Mittelwertbildung

von Jens K. (mister232)


Lesenswert?

Jetzt habe ich allerdings ein anderes Problem. Mein Multimeter 
(eigentlich ziemlich genau) zeigt einen Messwert von z.B. 0,986V. Am PC 
erhalte ich aber einen ADC-Wert von ca. 480, was nach meiner Umrechnung

U = (2,56 * ADC) / 1024

einen Spannungswert von 1,2V ergibt. Wodurch ensteht diese große 
Abweichung? Und vorallem, wie kann man diese Kompensieren?

von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:

>
1
>   #define READS 3
2
>   // oder 4 oder 5 nach belieben
3
>   uint16_t result=0;
4
>   for(i=0; i<READS; i++)
5
>   {  // Eine Wandlung
6
>     ADCSRA |= (1<<ADSC);
7
>     // Auf Ergebnis warten...
8
>     while(ADCSRA & (1<<ADSC));
9
> 
10
>     result += ADCW;  // aufsummieren
11
>   }
12
>   result >>= READS;    // Mittelwertbildung
13
>


Schön.
Aber leider falsch.

Jungs. Eure Compilerbauer sind keine Vollidioten. Die haben ihrem 
Compiler schon beigebracht, wann man eine DIvision durch ein Schieben 
ersetzen kann! Das brauchst du nicht selber im Code machen. Wenn die 
logische Operation eine Division ist, dann schreib auch Division hin. 
Dann machst du auch nicht so dumme Fehler, dass du 3 Messwerte 
summierst, dann aber durch 8 dividierst.

Schreib den Code so, wie ihn jeder erwarten würde!
1
  #define READS 8U
2
3
  uint16_t result=0;
4
  for(i=0; i<READS; i++)
5
  {  // Eine Wandlung
6
    ADCSRA |= (1<<ADSC);
7
    // Auf Ergebnis warten...
8
    while(ADCSRA & (1<<ADSC));
9
10
    result += ADCW;  // aufsummieren
11
  }
12
13
  result /= READS;    // Mittelwertbildung
14
}

Und wenn der Wert bei READS derart ist, dass man die Division durch eine 
SChiebeoperation ersetzen kann (kurz gesagt: wenn er eine 2-er Potenz 
ist), dann ersetzt der Compiler die Division durch eine 
Schiebeoperation. Das ist nichts, worüber man sich als Programmierer 
groß Gedanken machen muss. Wenn schon, dann mach dir darüber Gedanken, 
wie du den Wert bei READS so schreiben kannst, dass es möglichst 
schwierig ist, eine Nicht-2-er Potenz hinzuschreiben. Aber die 
Optimierung fürs Schieben überlässt du trotzdem dem Compiler. Denn 
irgendwann kommt ein genialer Nachfolger, der aus dem Konstrukt
1
#define NR_READ_BASE  3
2
#define READS (1 << NR_READ_BASE )
mit dem du eine 2-er POtenz erzwingen willst, dann doch wieder eine 
schöde 5 für die Anzahl der Messungen macht und dann erst mal 10 Minuten 
suchen muss, bis er rausfindet warum seine Werte nicht stimmen.

: Bearbeitet durch User
von Jens K. (mister232)


Lesenswert?

Ich habe auch keine Schiebeoperation eingetzt? Ich habe die Werte 
einfach aufsummiert und am Ende durch die Anzahl der Aufsummierungen 
dividiert. Das ergibt dann doch den Mittelwert oder sehe ich das falsch?

von Karl H. (kbuchegg)


Lesenswert?

Jens K. schrieb:
> Ich habe auch keine Schiebeoperation eingetzt? Ich habe die Werte
> einfach aufsummiert und am Ende durch die Anzahl der Aufsummierungen
> dividiert. Das ergibt dann doch den Mittelwert oder sehe ich das falsch?

Passt schon.

Aber es gibt immer Schlauberger, die meinen, sie müssten selbst im C 
Code 'clevere' Optimierungen anbringen, die jeder Wald und Wiesen 
Nasenpopel Compiler, der in den letzten 40 Jahren geschrieben wurde, 
perfekt und aus dem FF beherrscht.
Und pikanterweise hauen sie sich dann bei dieser 'cleveren Optimierung' 
einen Hund hinein, der dem Compiler nicht passiert wäre.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Jens K. schrieb:
> Jetzt habe ich allerdings ein anderes Problem. Mein Multimeter
> (eigentlich ziemlich genau) zeigt einen Messwert von z.B. 0,986V. Am PC
> erhalte ich aber einen ADC-Wert von ca. 480, was nach meiner Umrechnung
>
> U = (2,56 * ADC) / 1024
>
> einen Spannungswert von 1,2V ergibt. Wodurch ensteht diese große
> Abweichung? Und vorallem, wie kann man diese Kompensieren?

Hast du schon mal zurückgerechnet:
vom Messwert und der bekannten Spannung auf die Referenzspannung.

Denn die Referenzspannung ist vieles, vor allen Dingen ist sie in der 
Höhe stabil und konstant. Sie ist allerdings nicht 2.56V.
 2.56V ist ein typischer Wert, der im Einzelfall abweichen kann und für 
dein µC Examplar festgestellt werden müsste. Welche Spannung misst du 
denn am AREF Pin?

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Karl Heinz schrieb:
> Schön.
> Aber leider falsch.

das begründe mal du Schlaumeier ! was soll daran falsch sein !

EDIT ah das hier Zeile vergessen
for(i=0; i<(1<<READS); i++)

hättest du auch sagen können

> Jungs. Eure Compilerbauer sind keine Vollidioten.

das mag sein und interessiert mich nicht die Bohne !
kenne ich alle Kompilerbauer ? weiss ich wie die ticken ? und nein ich 
habe keinen Bock in den compilierten Code zu schauen.
Ob und wie die Bauer optimieren ist mir doch Banane, glauben tut man wo 
anders, DU glaubst das alle Compilerbauer das richtig machen, ich weiss 
es nicht, schaue auch nicht nach und bleibe bei meinem Code der nicht 
falsch ist und im Ergebnis dasselbe wie die Division liefert.

: Bearbeitet durch User
von Jens K. (mister232)


Lesenswert?

Hast recht Karl Heinz. An AREF messe ich 2,062V. Ist das ein plausibler 
Wert?

von Joachim B. (jar)


Lesenswert?

ich hatte auch noch nie die Aref messen können die ich eingestellt 
hatte.

Deswegen messe ich die am AREF Pin und nehme den Messwert als 
Rechengrundlage und nicht die theoretisch eingestellte AREF

von M. K. (sylaina)


Lesenswert?

Welchen Sinn macht es eigentlich die uart.h in der adc.c zu 
implementieren? Nur so aus Interesse.

von Jens K. (mister232)


Lesenswert?

Keinen :-)
Sind noch Relikte aus einem anderen Versuch ;-)

von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:
> Karl Heinz schrieb:
>> Schön.
>> Aber leider falsch.
>
> das begründe mal du Schlaumeier ! was soll daran falsch sein !
>
> EDIT ah das hier Zeile vergessen
> for(i=0; i<(1<<READS); i++)
>
> hättest du auch sagen können

Ja, wenn man darauf aufmerksam gemacht wird, ist es immer leicht 
hinterher Fehler zu finden. Bei den paar Zeilen Code ist das ja auch 
keine Hexerei.

>> Jungs. Eure Compilerbauer sind keine Vollidioten.
>
> das mag sein und interessiert mich nicht die Bohne !

Schön.
Dann mach weiter deine Blödsinnsfehler. Aber hör auf, anderen deinen 
Unsinn einzureden.

> und bleibe bei meinem Code der nicht falsch ist

Doch, das waer er. Nachweislich.

Was du programmierst oder nicht programmierst ist mir wurscht. Und auch 
wenn ich auch manchmal Fehler im Code habe, den ich hier veröffentliche, 
so sind es wenigstens nicht solche 'Ich schiess mir selber ins Knie und 
bin auch noch stolz drauf was für ein toller Trick-Programmierer ich 
doch nicht bin' Fehler. Und vor allen Dingen reagiere ich anders, wenn 
mir jemand einen Fehler in meinem veröffentlichten Code zeigt.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Karl Heinz schrieb:
> Und vor allen Dingen reagiere ich anders, wenn
> mir jemand einen Fehler in meinem veröffentlichten Code zeigt.

da hast du zum Teil recht, nur frag ich mich wo du einen Fehler gezeigt 
hast ?
Du hast erst mal falsch gerufen, Fehler gezeigt hast du nicht, klingt 
komisch ist aber so.

Ich weiss auch nicht was dich am Code stört nur weil er dir nicht 
schmeckt.

Ich freue mich für dich das du alles besser weisst, nur was hat das mit 
dem zu tun ob der eine lieber shiftet und der andere dividiert und 
deiner Meinung nach alle Compiler das gleich optimieren ?

sonst zickst du doch nicht so rum.......

von lesen will gelernt sein (Gast)


Lesenswert?

Joachim B. schrieb:
> da hast du zum Teil recht, nur frag ich mich wo du einen Fehler gezeigt
> hast ?
> Du hast erst mal falsch gerufen, Fehler gezeigt hast du nicht, klingt
> komisch ist aber so.

eventuell hier?

Karl Heinz schrieb:
> Joachim B. schrieb:
>
>>>   #define READS 3
>>   // oder 4 oder 5 nach belieben
>>   uint16_t result=0;
>>   for(i=0; i<READS; i++)
>>   {  // Eine Wandlung
>>     ADCSRA |= (1<<ADSC);
>>     // Auf Ergebnis warten...
>>     while(ADCSRA & (1<<ADSC));
>>
>>     result += ADCW;  // aufsummieren
>>   }
>>   result >>= READS;    // Mittelwertbildung
>>
> [..]
> Dann machst du auch nicht so dumme Fehler, dass du 3 Messwerte
> summierst, dann aber durch 8 dividierst.

von Joachim B. (jar)


Lesenswert?

lesen will gelernt sein schrieb:
> eventuell hier?

lesen will gelernt sein schrieb:

>> Dann machst du auch nicht so dumme Fehler, dass du 3 Messwerte
>> summierst, dann aber durch 8 dividierst.

dazu müsste man verstehen was gemeint war, lesen gelernt reicht manchmal 
nicht !

der Fehler war hier:

for(i=0; i<READS; i++)


die Korrektur so:

for(i=0; i<(1<<READS); i++)

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:

>
> der Fehler war hier:
>

wir alle wissen was der eigentliche Fehler war und wie er entstanden 
ist.

Das ändert aber nichts daran, dass ein unbedarfter Neuling est mal 
falsche Messergebnisse kriegt, die er ganz sicher nicht mit einem 
fehlenden Links-Shift in der Abfragebedingung der for-Schleife in 
Beziehung setzt.

Aus Fehlersicht rückwärts aufgerollt, hat er erst mal falsche 
Ergebnisse. Die entstehen dadurch, dass nur 3 Messwerte summiert werden, 
aber durch 8 zur Mittelwertbildung dividiert wird. Das wiederrum 
entsteht dadurch, dass die 3 eiegentlich 8 sein sollten, was wiederrum 
auf den vergessenen Shift zurückzuführen ist.

Das ist bereits eine Latte von Schlussfolgerungen und 
Rückwärtsverfolgungen, die gerade ein Neuling erst mal bringen muss.
Und warum das Ganze?
Wegen nichts!

Und genau das ist der springende Punkt. Es ist ein Fehler der 'wegen 
nichts' entstanden ist. Denn diese Optimierung macht jeder Compiler 
genauso gut. Nur macht der eben dabei keinen Fehler.

von Dietrich L. (dietrichl)


Lesenswert?

Jens K. schrieb:
> An AREF messe ich 2,062V. Ist das ein plausibler Wert?

Dazu gibt es das Datenblatt. Da steht:

Table 121. ADC Characteristics, Single Ended channels, TA = -40°C to 
85°C

Symbol Parameter                  Condition  Min   Typ  Max Units
-----------------------------------------------------------------
VINT   Internal Voltage Reference            2.3  2.56  2.7   V


Dein Wert ist daher außerhalb der Spezifikation - warum auch immer ...

Gruß Dietrich

von Joachim B. (jar)


Lesenswert?

Karl Heinz schrieb:
> Jungs. Eure Compilerbauer sind keine Vollidioten. Die haben ihrem
> Compiler schon beigebracht, wann man eine DIvision durch ein Schieben
> ersetzen kann! Das brauchst du nicht selber im Code machen.

vielleicht weil ich mal an Assembler dachte, da ist Schieben normaler 
als DIV

Karl Heinz schrieb:
> Aber es gibt immer Schlauberger, die meinen, sie müssten selbst im C
> Code 'clevere' Optimierungen anbringen,

und was soll das willst du moderieren oder provozieren ? (das ist 
einfach nur unwürdig)

Karl Heinz schrieb:
> Ja, wenn man darauf aufmerksam gemacht wird, ist es immer leicht
> hinterher Fehler zu finden.

den habe ich selber gefunden (c&p Fehler Zeile verrutscht) habe nur 
Fehler gelesen und rübergeschaut, aber egal (der Moderator hat immer 
Recht)

Karl Heinz schrieb:
> Aus Fehlersicht rückwärts aufgerollt, hat er erst mal falsche
> Ergebnisse.

klar und wer hier ohne Fehler ist der poste den perfekten Code ....

Karl Heinz schrieb:
> Und genau das ist der springende Punkt. Es ist ein Fehler der 'wegen
> nichts' entstanden ist. Denn diese Optimierung macht jeder Compiler
> genauso gut.

na wenn es jeder Compiler macht dann ist es der Streit um Kaisers Bart 
weil immer dasselbe rauskommt, also kann ich weiter so verfahren, mir 
gefällts und bis auf den Fehler beim Einstellen sehe ich auch nix 
verwerfliches was derlei derbe Worte eines Moderator rechtfertigt.

von Karl H. (kbuchegg)


Lesenswert?

Joachim B. schrieb:

>> Aber es gibt immer Schlauberger, die meinen, sie müssten selbst im C
>> Code 'clevere' Optimierungen anbringen,
>
> und was soll das willst du moderieren oder provozieren ? (das ist
> einfach nur unwürdig)

Nach dem 3000-ten Fehler, den du langwierig gesucht hast und der wieder 
mal auf einen cleveren Hack des Programmierers zurückzuführen ist, mit 
dem er sich selbst ausgetrickst hat, würdest du auch allergisch auf 
derartige clevere Hacks reagieren.

von Joachim B. (jar)


Lesenswert?

Karl Heinz schrieb:
> Nach dem 3000-ten Fehler....., würdest du auch allergisch auf
> derartige clevere Hacks reagieren.

nun ja verstehe ich schon, auch ein Grund warum ich nicht mehr Moderator 
sein mag. Manchmal muss man sich aber gerade als Moderator denn als User 
eine Pause gönnen um danach wieder gelassener zu werden.

Es ist doch nix weiter passiert, oder ?

Ich hatte einen c & p Fehler, der Code funktioniert, ob sinnvoll darüber 
kann man ja unterschiedlicher Meinung sein. Man muss auch mal andere, 
nicht unbedingt falsche, Sichtweisen akzeptieren können sonst hat man es 
unnötig schwer.

Ich denke halt du warst genervt was nicht zwingend ausschliesslich an 
meinem c & p Fehler lag.

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.