mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Anfänger Problem mit ADC aus Tutorial


Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

um den ADC kennenzulernen habe ich mir das GCC durchgelesen.
Ich habe eigentlich nichts spektakuläres vor, möchte lediglich einen ADC 
Wert einlesen und 2 LED's dazu leuchten lassen.
Eine LED soll leuchten, wenn die Spannung größer 0,5V (bzw. 500mV)ist, 
aber kleiner 1,1V (bzw. 1100mV). Eine zweite LED soll zusätzlich mit 
Leuchten, wenn die eingelesene Spannung größer als 1,1V ist (1100mV).
CPU: Atmega8
AVCC ist mir VCC verbunden
AGND ist mit GND verbunden
zwischen AVCC und AGND ist ein 100nF Kondensator
Poti als Spannungsteiler an 5V und GND, Schleifer auf den ADC0

Eingelesen wird am Pin ADC0 mit dem Quellcode aus dem Tutorial.
In meinem Hauptprogramm wandle ich den ADC Wert nur wieder zurück in 
eine Spannung in mV und lasse diese durch die LED's ausgeben.
Leider leuchtet, egal wie ich das Poti einstelle, nur die erste LED.
// ----------------------------------------------------------------------------
// INCLUDES
// ----------------------------------------------------------------------------

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>

// ----------------------------------------------------------------------------
// DEFINES
// ----------------------------------------------------------------------------
#ifndef F_CPU
#define F_CPU           8000000UL                   // processor clock frequency 8Mhz
#endif

#define LED      PB1    //Pin der LED die leuchtet bei geschlossenem Reedkontakt
#define LED1    PB2    //Zeigt die Frequenz an


// ----------------------------------------------------------------------------
// Funktionsdeklarationen
// ----------------------------------------------------------------------------
void init(void);
uint16_t readADCWert (uint8_t mux);

int main()
{
//variablen für das Programm
uint16_t ADCWert;
const char RefSpannung = 5; 

  init();

  while (1){

    ADCWert = readADCWert (0);               // ruft Funktion auf und liest im ADMUX Register den Wert von MUX0 = ADC 0
    
/* Berechnung des ADC-Wertes: V = (ADCWert * Referenzspannung) / 2^Auflösung
             z.B. V = 592 * 5V / 2^10 = 592*5/1024 = 2,89V
                
*/
    ADCWert = ADCWert*RefSpannung/1024*1000;        // Berechnung der Spannung in mV

    if (ADCWert >= 500 && ADCWert >= 1100){        // wenn der Wert  zwischen 500mV und 1100 mV liegt leuchtet 1 LED
    PORTB &= ~(1 << LED);
    
    }
    
    else if (ADCWert > 1100){                // wenn der Wert größer als 1100mV ist, leuchten LED 1 und LED 2
    PORTB &= ~ (1 << LED);
    PORTB &= ~ (1 << LED1);
    
    }
    
    else{
    }
  }

}

// ----------------------------------------------------------------------------
// Funktionen
// ----------------------------------------------------------------------------


void init(void) {

    // ========================================================================
  // PORTB konfigurieren
  //
  //  B1, B2 --> Ausgang für LED, active-low
  // ========================================================================
  
  DDRB = 0x00;      
  DDRB |= (1 << DDB1) | (1 << DDB2);   
  PORTB |= (1 << LED) | (1 << LED1);  //LED am Anfang ausschalten
  
  
  return;
}

//=========================================================================
// ADC-Wandlung (10-bit)
//=========================================================================

uint16_t readADCWert (uint8_t mux)
{
  uint8_t i;                    // Schleifenzähler für die 4-fach Messung (bildet Durchschnitt)
  uint16_t result;                  // Speichert das Ergebnis der Funktion und gibt dieses zurück. Wert zwischen 0...1023 (10-bit)
  const uint8_t  anzahl_messungen=4;          // Hier wird die Anzahl der Messungen, aus denen der Mittelwert gebildet werden soll, festgelegt
 
  ADMUX = mux;                              // wird der Funktion übergeben, entspricht dem Kanal der ausgelesen werden soll (0=ADC0, 1ADC1, etc.)
  ADMUX &= (~(1<<REFS1) | (1<<REFS0));       // Spannung am Pin AREF dient als Referenz, es muss VCC anliegen (Beschaltung beachten)
                     
  
  ADCSRA = (1<<ADEN) | (1<<ADPS1) | (1<<ADPS2);  // Vorteiler wird eingestellt, ADC wird aktiviert
                               
 
  /* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man liest
     also einen Wert und verwirft diesen, um den ADC "warmlaufen zu lassen" */
   
  ADCSRA |= ( 1 << ADSC );                   // startet eine ADC-Wandlung 
  while ( ADCSRA & (1<<ADSC) ) {
     ;                           // auf Abschluss der Konvertierung warten, das bit im ADSC Register wird nach erfolgreicher Wandlung gelöscht, Schleife somit beendet
  }
  result = ADCW;                    // ADCW muss einmal gelesen werden, sonst wird Ergebnis der nächsten Wandlung nicht übernommen. (Ergebnis wird im Register ADCW gespeichert)
 
  /* Eigentliche Messung - Mittelwert aus 4 aufeinanderfolgenden Wandlungen */
  result = 0;                     // Dummy Readout Messwert verwerfen und result zu 0 setzen für neue Messung
  
  for( i=0; i<anzahl_messungen; i++ )        // Schleife für 4 aufeinanderfolgende Werte die in result zunächst aufaddiert werden
  {
    ADCSRA |= (1<<ADSC);                   // eine Wandlung "single conversion" startet
    while ( ADCSRA & (1<<ADSC) ) {
      ;                         // auf Abschluss der Konvertierung warten
    }
    result += ADCW;                    // Wandlungsergebnisse aufaddieren
  }
  ADCSRA &= ~(1<<ADEN);                     // ADC deaktivieren
 
  result /= anzahl_messungen;                       // Summe durch anzahl_messungen teilen = arithm. Mittelwert
 
  return result;                  // Funktion gibt bei Aufruf die Variable "result" zurück. Diese enthält den Mittelwert aus den ADC Messunge
}


Autor: Michael U. (amiga)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

    if (ADCWert >= 500 && ADCWert >= 1100){        // wenn der Wert 
zwischen 500mV und 1100 mV liegt leuchtet 1 LED

wenn ADCWert grüßer gleich 500 und größer gleich 1100 ist???? ;-)

PS: ein Computer macht nicht immer, was man will.
Er macht aber immer, was man ihm sagt...

Gruß aus Berlin
Michael

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Michael,

ja, das ist wohl ein kleiner Fehler von mir. Habe das nun geändert auf:
if (ADCWert >= 500 && ADCWert <= 1100){        // wenn der Wert
zwischen 500mV und 1100 mV liegt leuchtet 1 LED

Nun ist es aber so, dass egal welche Spannung anliegt, BEIDE LED's 
leuchten, auch wenn ich das Poti ganz zudrehe und am ADC0 0 Volt 
anliegen?!?

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
HI

Hälst du es wirklich für sinnvoll, bei jeder Messung den ADC neu zu 
initialisieren?

> ADMUX &= (~(1<<REFS1) | (1<<REFS0));

Die Zeile ist sinnlos. Dein ADC dürfte mit externer Referenzspannung 
laufen. Ist das gewollt.

MfG Spess

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

danke spess, das mit der externen Referenz war nicht gewollt.
Habe nun 5V auch an AREF angelegt und siehe da, er misst ziemlich genau. 
Bei 0,52V geht die erste LED an und bei 1,04V die 2. LED mit (mit 
Multimeter gemessen).

Vielen Dank, Problem geklärt :-)

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

>Vielen Dank, Problem geklärt :-)

Nein. Setze REFS1:REFS0 auf 01. Damit wird VCC intern an AREF 
geschaltet. Und an das AREF-Pin einen 100nf Kondensator nach Masse. 
Dann ist das Problem ordentlich geklärt.

MfG Spess

Autor: bix (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielleicht weil Du in der while() Schleife in main immer nur 
einschaltest, aber nie aus?

Autor: Walter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>    ADCWert = ADCWert*RefSpannung/1024*1000;        // Berechnung der Spannung in 
mV

Rechne dir mal zu Fuß für den ADCWert=200 Aus was da rauskommt,
(du solltest 0 rauskriegen)

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
bix schrieb:
> Vielleicht weil Du in der while() Schleife in main immer nur
> einschaltest, aber nie aus?

Wie meinst du das? Der ADC wird doch von der Funktion aufgerufen, 
eingeschaltet und dann nach der Konvertierung (bzw. 1 Dummy Readout+4 
zur Mittelwert Bildung) abgeschaltet?

Walter schrieb:
> Rechne dir mal zu Fuß für den ADCWert=200 Aus was da rauskommt,
> (du solltest 0 rauskriegen)

Wenn ich für ADCWert 200 einsetze, rechen ich:
200*5/1024*1000=976,56mV

Wie meinst du also das, es sollte 0 herauskommen?

Autor: JTR (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Naja, ich habe es so verstanden.

Deine ADCWert Variable wurde als INT deklariert, dh eine Ganzzahl.

Wenn du nun 200*5=1000 rechnest und dann diese 1000/1024=0,97...
Aber in deinem Integer steht dann 0. Und danach rechnest du 0*1000=0.
Also müsste bei 200 für ADCWert 0 rauskommen.

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

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:

> Wenn ich für ADCWert 200 einsetze, rechen ich:
> 200*5/1024*1000=976,56mV
>
> Wie meinst du also das, es sollte 0 herauskommen?

Jetzt rechnest du noch einmal.
Aber diesmal machst du es genau gleich, wie es auch dein µC macht:
Nach jedem Rechenschritt lässt du die dabei entstehenden 
Nachkommastellen unter den Tisch fallen.

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

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:
> bix schrieb:
>> Vielleicht weil Du in der while() Schleife in main immer nur
>> einschaltest, aber nie aus?
>
> Wie meinst du das? Der ADC wird doch von der Funktion aufgerufen,
> eingeschaltet und dann nach der Konvertierung (bzw. 1 Dummy Readout+4
> zur Mittelwert Bildung) abgeschaltet?

er redet nicht vom ADC.
Er redet von den LED.
Die werden zwar eingeschaltet, aber nie aus.
Wenn die LED erst einmal leuchtet, dann leuchtet sie immer weiter, 
selbst dann wenn sie laut ADC Wert eigentlich schon ausgeschaltet werden 
sollte.

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

Bewertung
0 lesenswert
nicht lesenswert
PS: Mann muss nicht den ADC Wert in eine Spannung umrechnen.
Man kann auch die Grenzenwerte von einer Spannung in die dazu 
notwendigen internen ADC Werte zurückrechnen.
Das hat den Charme, dass diese Grenzen innerhalb eines Programmlaufs 
konstant sind und man diese Umrechnung daher nur ein einziges mal machen 
muss. Das kann zb auch bedeuten: zu Fuss mit dem Taschenrechner.
Dann muss der µC überhaupt nicht mehr rechnen. Der gemessene ADC Wert 
wird mit dem Grenzwert (ausgedrückt in ADC Einheiten) verglichen und 
fertig.

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

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:

> Habe nun 5V auch an AREF angelegt und siehe da, er misst ziemlich genau.
> Bei 0,52V geht die erste LED an und bei 1,04V die 2. LED mit (mit
> Multimeter gemessen).

Das ist nicht ziemlich genau

Dein ADC kann theoretisch auflösen:

  5V / 1024 =  0.0048 Volt

Also 4 Millivolt. OK. Surch Schaltungsaufbau und Messfehler kommt da 
noch ein kleiner Fehler dazu. Seis drumm: sagen wir er misst auf 10 
Millivolt genau. Das sind 0.010 Volt. Und ich gestehe dir diesen Fehler 
sogar +- zu! (Das ist ein riesieger Fehler. Wenn der wirklich so groß 
wäre, würde es sich um einen Schätzprügel und nicht um einen 10Bit ADC 
handeln)

Deine Vorgabe waren 0.500V genau als Schaltschwelle. D.h. die erwartete 
Spannung, bei der der Schaltvorgang ausgelöst wird (erinnere dich an die 
10Millivolt), liegt irgendwo zwischen 0.49 und 0.51. Und da hab ich dir 
bei der Messung schon einen enormen Fehler zugebillgt!

Die Schwelle kommt bei 0.52 Volt. Das ist weit vom erwarteten Wert 
entfernt und sollte dir eigentlich ein Hinweis sein, dass irgendetwas 
nicht stimmt!

Fazit: Nicht jeden Wert aus einem Computer oder Taschenrechner kritiklos 
übernehmen und glauben, sondern ruhig auch einmal nachrechnen ob der 
Wert auch plausibel ist.
Es gibt da draussen schon genug Menschen, die mit der Philosophie leben: 
Das hat der Computer ausgespuckt, also muss es auch stimmen. Und wenn 
das Navi sagt, da wäre eine Brücke, dann ist da auch eine Brücke! Blöd 
nur, wenn die Fähre dann gerade am anderen Ufer steht.

Autor: Sven (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Jetzt rechnest du noch einmal.
>
> Aber diesmal machst du es genau gleich, wie es auch dein µC macht:
>
> Nach jedem Rechenschritt lässt du die dabei entstehenden
>
> Nachkommastellen unter den Tisch fallen.

Ok, also z.B. in float rechnen, dann sollte das klappen mit den 
Nachkommastellen.

Karl heinz Buchegger schrieb:
> er redet nicht vom ADC.
>
> Er redet von den LED.
>
> Die werden zwar eingeschaltet, aber nie aus.
>
> Wenn die LED erst einmal leuchtet, dann leuchtet sie immer weiter,
>
> selbst dann wenn sie laut ADC Wert eigentlich schon ausgeschaltet werden
>
> sollte.

Ok, das habe ich verstanden.

Karl heinz Buchegger schrieb:
> PS: Mann muss nicht den ADC Wert in eine Spannung umrechnen.
>
> Man kann auch die Grenzenwerte von einer Spannung in die dazu
>
> notwendigen internen ADC Werte zurückrechnen.


Das ginge aber nur, wenn ich, wie hier im Beispiel, wirklich feste Werte 
als Grenzwerte nehme, oder? Wenn ich etwas "stufenlos" regeln oder 
umrechnen  möchte, muss ich das mit einer zuvor ermittelten Formel 
umrechnen oder nicht?
Beispiel: Wenn ich eine Temperatur messen möchte, die jeden Tag anders 
ist und ich diese mit dem ADC und dem Poti einstellen kann. Bei 
erreichen der Temperatur soll eine LED angehen. Da ich aber jeden Tag 
eine neue Temperatur einstelle und vorher die Temperatur noch nicht 
weiß, muss ich es ja zwangsläufig umrechen im µC Programm, oder?

Karl heinz Buchegger schrieb:
> Die Schwelle kommt bei 0.52 Volt. Das ist weit vom erwarteten Wert
>
> entfernt und sollte dir eigentlich ein Hinweis sein, dass irgendetwas
>
> nicht stimmt!


Ist dieser Fehler wohl eher hardwareseitig zu vermuten, oder? Ich habe 
im Moment keinen Kondensator zwischen AREF und Masse gehabt. Mögliche 
Ursache?

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

Bewertung
0 lesenswert
nicht lesenswert
Sven schrieb:

> Ok, also z.B. in float rechnen, dann sollte das klappen mit den
> Nachkommastellen.

Du kannst auch probehalber die Berechnung umstellen :-)

Du dividierst durch 1024 um dann mit 1000 zu multiplizieren.
Wenn du das umdrehst, also zuerst mal 1000 und erst dann durch 1024, was 
passiert dann? (Da muss man dann immer aufpassen, dass die 
Zwischenergebnisse nicht zu gross werden).

Oder: Wenn du nicht durch 1024 dividierst, sondern durch 102, brauchst 
du auch nicht mal 1000 zu nehmen, sondern nur mal 100. Wie wirkt sich 
das auf das Ergebnis aus? Ist dieser Fehler (102 statt 102.4) überhaupt 
merkbar?

Oder: Anstatt durch 1024 kann man auch durch 512 dividieren und dann mal 
500 anstelle von 1000. Wie wirkt sich diese Änderung aus?

Oder ...

Es gibt viele Möglichkeiten. Das schlimmste was du aber tun kannst, ist 
mit einer naiven mathematischen Vorstellung an die Sache ranzugehen, 
dass Berechnungen im Computer genauso funktionieren wie in der 
Mathematik. In der Mathematik hast du es mit idealisierten Zahlen zu 
tun, mit denen man alles machen kann. Im Computer gibt es immer 
irgendwelche Einschränkungen, die es zu beachten gilt. Oft kann eine 
klitzekleine Umstellung in der Berechnung, die mathematisch betrachtet 
ein völlig identisches Ergebnis bringen sollte, im Computer sehr viel 
ausmachen.

Als Faustregel kannst du dir merken: Du möchtest in einer Berechnung 
Divisionen erst möglichst ganz zum Schluss machen. Denn bei einer 
Division verlierst du im Allgemeinen immer Genauigkeit. Ein Computer 
kann nun mal nicht beliebig viele Stellen hinter dem Komma bearbeiten. 
Sein Speicherplatz ist begrenzt.

>> Man kann auch die Grenzenwerte von einer Spannung in die dazu
>>
>> notwendigen internen ADC Werte zurückrechnen.
>
>
> Das ginge aber nur, wenn ich, wie hier im Beispiel, wirklich feste Werte
> als Grenzwerte nehme, oder?

Warum?

> Wenn ich etwas "stufenlos" regeln oder
> umrechnen  möchte, muss ich das mit einer zuvor ermittelten Formel
> umrechnen oder nicht?

Genau. Aber anstelle den ADC Wert in dir genehme Einheiten umzurechnen 
und mit einem Grenzwert zu vergleichen, kann man ja auch den umgekehrten 
Weg gehen. Man rechnet die Grenze in ADC Einheiten zurück.

> Beispiel: Wenn ich eine Temperatur messen möchte, die jeden Tag anders
> ist und ich diese mit dem ADC und dem Poti einstellen kann. Bei
> erreichen der Temperatur soll eine LED angehen. Da ich aber jeden Tag
> eine neue Temperatur einstelle und vorher die Temperatur noch nicht
> weiß, muss ich es ja zwangsläufig umrechen im µC Programm, oder?

Ja, musst du.
Aber: Du musst nur einmal umrechnen, anstatt dauern den ADC Wert 
umzurechnen.

Im Prinzip ist diese Rückrechnung die Umkehrung von oben. Da hast du 
schon gesehen, dass dir durch / 1024 * 1000 etas verloren geht.

>
> Ist dieser Fehler wohl eher hardwareseitig zu vermuten, oder? Ich habe
> im Moment keinen Kondensator zwischen AREF und Masse gehabt. Mögliche
> Ursache?

Möglich.
Aber denk noch einmal nach.
Wenn ein ADC Wert von 200 (der ganz sicher nicht 0 Volt entspricht) bei 
deiner Berechnung einer Millivoltangabe von 0 entspricht (kommt bei 
deiner Rechnerei raus), dann kann in der Umrechnung irgendetwas nicht 
stimmen :-) Was, hast du ja bereits gesehen: Dadurch dass du die 
Umrechnung von ADC Werten in Millivolt ungeschickt gemacht hast, hast du 
unnötigerweise Hausnummern herausbekommen, die zwar im groben deinen ADC 
Wert in Millivolt umrechnen, aber genau darf man nicht hinsehen.

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.